diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index 1ff3720ba091f..0000000000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -prepare: - fetch: - - url: "https://raw.githubusercontent.com/automattic/jetpack/master/.github/codeclimate-base.yml" - path: "codeclimate-base.yml" - diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index c6f3cf49588a3..0000000000000 --- a/.editorconfig +++ /dev/null @@ -1,24 +0,0 @@ -# This file is for unifying the coding style for different editors and IDEs -# editorconfig.org - -# WordPress Coding Standards -# https://make.wordpress.org/core/handbook/coding-standards/ - -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_style = tab - -[*.yml] -indent_style = space -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false - -[{*.txt,wp-config-sample.php}] -end_of_line = crlf diff --git a/.eslines.json b/.eslines.json deleted file mode 100644 index e9b5fee85d291..0000000000000 --- a/.eslines.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "branches": { - "default": [ "downgrade-unmodified-lines", "filter-when-format" ], - "master": [ "filter-parsing-errors" ] - }, - "processors": { - "downgrade-unmodified-lines": { - "remote": "origin/master", - "rulesNotToDowngrade": [ "no-unused-vars" ] - }, - "filter-when-format": { - "rulesToIgnore": [ "indent" ] - } - } -} diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 053cb775f1a48..0000000000000 --- a/.eslintignore +++ /dev/null @@ -1,24 +0,0 @@ -*.min.js -/3rd-party/debug-bar/debug-bar.js -/_inc/blocks -/_inc/build -/_inc/client/**/test/*.js -/_inc/jetpack-modules.models.js -/_inc/jquery.spin.js -/_inc/postmessage.js -/_inc/spin.js -/docker/wordpress-develop/ -/docker/wordpress/ -/languages/ -/modules/custom-css/custom-css/js/core-customizer-css-preview.js -/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js -/modules/custom-css/custom-css/js/core-customizer-css.js -/modules/lazy-images/js/lazy-images.js -/modules/widgets/search/js/search-widget-admin.js -/vendor/ - -# Temporary ignore until Jest is set up -/extensions/**/test/ - -# Temporary ignore until Instant Search is moved into modules -/_inc/search diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 98a24e8057972..0000000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,145 +0,0 @@ -module.exports = { - root: true, - parser: 'babel-eslint', - extends: [ 'wpcalypso/react', 'plugin:jsx-a11y/recommended', 'prettier', 'prettier/react' ], - env: { - browser: true, - es6: true, - mocha: true, - node: true, - jquery: true, - }, - parserOptions: { - ecmaVersion: 2019, - ecmaFeatures: { - jsx: true, - }, - }, - settings: { - react: { - version: 'detect', // React version. "detect" automatically picks the version you have installed. - }, - }, - plugins: [ 'jsx-a11y', 'lodash', 'jsdoc' ], - rules: { - // REST API objects include underscores - camelcase: 0, - 'comma-spacing': 2, - curly: 2, - 'computed-property-spacing': [ 2, 'always' ], - 'func-call-spacing': 2, - 'jsx-quotes': [ 2, 'prefer-double' ], - 'key-spacing': 2, - 'keyword-spacing': 2, - 'lodash/import-scope': [ 2, 'member' ], - 'max-len': 0, // Ignored for Jetpack - 'new-cap': [ 2, { capIsNew: false, newIsCap: true } ], - 'no-else-return': 2, - 'no-extra-semi': 2, - 'no-multiple-empty-lines': [ 2, { max: 1 } ], - 'no-multi-spaces': 2, - 'no-restricted-imports': [ 2, 'lib/sites-list', 'lib/mixins/data-observe' ], - 'no-restricted-modules': [ 2, 'lib/sites-list', 'lib/mixins/data-observe' ], - 'no-shadow': 2, - 'no-spaced-func': 2, - 'no-trailing-spaces': 2, - // Allows Chai `expect` expressions - 'no-unused-expressions': 0, - 'no-unused-vars': 2, - 'no-var': 2, - 'object-curly-spacing': [ 2, 'always' ], - 'operator-linebreak': [ - 2, - 'after', - { - overrides: { - '?': 'before', - ':': 'before', - }, - }, - ], - 'padded-blocks': [ 2, 'never' ], - 'prefer-const': 2, - 'react/jsx-curly-spacing': [ 2, 'always' ], - 'react/jsx-no-bind': 2, - // 'react/jsx-space-before-closing': 2, - 'react/jsx-tag-spacing': [ 2, { beforeSelfClosing: 'always' } ], - 'react/no-danger': 2, - 'react/no-did-mount-set-state': 2, - 'react/no-did-update-set-state': 2, - 'react/no-is-mounted': 2, - 'react/prefer-es6-class': 1, - semi: 2, - 'semi-spacing': 2, - 'space-before-blocks': [ 2, 'always' ], - 'space-in-parens': [ 2, 'always' ], - 'space-infix-ops': [ 2, { int32Hint: false } ], - 'space-unary-ops': [ - 2, - { - overrides: { - '!': true, - }, - }, - ], - 'template-curly-spacing': [ 2, 'always' ], - 'wpcalypso/i18n-ellipsis': 2, - 'wpcalypso/i18n-no-collapsible-whitespace': 2, - 'wpcalypso/i18n-no-this-translate': 2, - 'wpcalypso/i18n-no-variables': 2, - 'wpcalypso/i18n-mismatched-placeholders': 2, - 'wpcalypso/import-docblock': 2, - 'wpcalypso/jsx-gridicon-size': 0, // Ignored for Jetpack - 'wpcalypso/jsx-classname-namespace': 0, // Ignored for Jetpack - 'jsx-a11y/label-has-for': [ - 2, - { - required: { - some: [ 'nesting', 'id' ], - }, - }, - ], - // Disabled rules for now. Ideally we should resolve all the errors these rules create. - 'wpcalypso/redux-no-bound-selectors': 0, - 'jsx-a11y/anchor-has-content': 0, - 'react/no-string-refs': 0, - 'jsx-a11y/anchor-is-valid': 0, - - // JSDoc plugin overrides - 'jsdoc/check-alignment': 1, // Recommended - 'jsdoc/check-examples': 1, - 'jsdoc/check-indentation': 1, - 'jsdoc/check-param-names': 1, // Recommended - 'jsdoc/check-syntax': 1, - 'jsdoc/check-tag-names': 1, // Recommended - 'jsdoc/check-types': 1, // Recommended - 'jsdoc/implements-on-classes': 1, // Recommended - 'jsdoc/newline-after-description': 1, // Recommended - 'jsdoc/no-undefined-types': 1, // Recommended - 'jsdoc/require-description': 1, - 'jsdoc/require-hyphen-before-param-description': 1, - 'jsdoc/require-jsdoc': 1, // Recommended - 'jsdoc/require-param': 1, // Recommended - 'jsdoc/require-param-description': 1, // Recommended - 'jsdoc/require-param-name': 1, // Recommended - 'jsdoc/require-param-type': 1, // Recommended - 'jsdoc/require-returns': 1, // Recommended - 'jsdoc/require-returns-check': 1, // Recommended - 'jsdoc/require-returns-description': 1, // Recommended - 'jsdoc/require-returns-type': 1, // Recommended - 'jsdoc/valid-types': 1, // Recommended - 'jsdoc/check-values': 1, - - // eslint 6.x migration - 'no-unused-vars': 1, - 'no-useless-escape': 1, - 'no-extra-boolean-cast': 1, - 'no-case-declarations': 1, - 'no-class-assign': 1, - 'no-redeclare': 1, - - // Workaround for ESLint failing to parse files with template literals - // with this error: "TypeError: Cannot read property 'range' of null" - 'template-curly-spacing': 'off', - }, -}; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 322e44682b1b1..0000000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,18 +0,0 @@ -############################################ -# Please fire off a PR to this file to own # -# or disavow any section of the codebase. # -############################################ - -# Individual Modules: -/modules/contact-form/ @georgestephanis -/modules/custom-css/ @georgestephanis -/modules/search/ @Automattic/jetpack-search - -# Other bits of the Codebase -/_inc/lib/debugger/ @kraftbj -/_inc/lib/jetpack-wpes-query-builder/ @gibrown - -# Functionality that is important to our partners -/class.jetpack-cli.php @automattic/jetpack-infinity -/bin/partner-provision.sh @automattic/jetpack-infinity -/bin/partner-cancel.sh @automattic/jetpack-infinity diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 608dcf54f9ffa..0000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,22 +0,0 @@ - - -#### Steps to reproduce the issue - -1. -2. -3. - -#### What I expected - -#### What happened instead - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 6bb4a341ff033..0000000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - - - -#### Steps to reproduce the issue - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -#### What I expected - - -#### What happened instead - - -**Screenshots** -If applicable, add screenshots to help explain your problem. - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 684861f2fc7bd..0000000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - -#### Is your feature request related to a problem? Please describe. - - -#### Describe the solution you'd like - - -#### Describe alternatives you've considered - - -#### Additional context - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 731ba0b6d8f33..0000000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,30 +0,0 @@ - - - -Fixes # - -#### Changes proposed in this Pull Request: - -* - -#### Is this a new feature or does it add/remove features to an existing part of Jetpack? -* If you're an Automattician, include a shortlink to the p2 discussion with Jetpack Product here. - -#### Does this pull request change what data or activity we track or use? - - - -#### Testing instructions: - - - - - - -* Go to '..' -* - -#### Proposed changelog entry for your changes: - -* diff --git a/.github/codeclimate-base.yml b/.github/codeclimate-base.yml deleted file mode 100644 index 6c4738419692a..0000000000000 --- a/.github/codeclimate-base.yml +++ /dev/null @@ -1,72 +0,0 @@ -# Basic configuration for CodeClimate. -# This basic configuration can then be used and extended up in other repositories, -# or in this one, using the prepare step: -# https://docs.codeclimate.com/docs/configuring-the-prepare-step -# -# Here is how your codeclimate file extending Jetpack's one should look like: -# prepare: -# fetch: -# - url: "https://raw.githubusercontent.com/automattic/jetpack/master/.github/codeclimate-base.yml" -# path: "codeclimate-base.yml" ---- -engines: - csslint: - enabled: true - duplication: - enabled: true - config: - languages: - - javascript - - php - fixme: - enabled: true - phpcodesniffer: - enabled: true - config: - standard: "WordPress" - phpmd: - enabled: true - eslint: - enabled: true - channel: "eslint-5" - config: - config: "/.eslintrc.js" - scss-lint: - enabled: true - markdownlint: - enabled: true -ratings: - paths: - - "**.css" - - "**.scss" - - "**.inc" - - "**.js" - - "**.jsx" - - "**.php" - - "**.md" -exclude_paths: - - "**/.github/**" - - "**/tests/**" - - "**.png" - - "**.jpg" - - "**.gif" - - "gulpfile.js" - - "composer.lock" - - ".phpcs.xml.dist" - - "**.json" - - "**.svnignore" - - "**.travis.yml" - - "**.gitignore" - - "**.eslintrc.js" - - "**.editorconfig" - - "**.pot" - - "**.txt" - - "**-min.js" - - "**-min.css" - - "**.dist" - - "**.sh" -checks: - method-lines: - enabled: false - file-lines: - enabled: false diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index a068bbd128f30..0000000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,5 +0,0 @@ -"[Status] Needs Package Release": -- 'packages/*' -- 'packages/*/*' -- 'packages/*/*/*' -- 'packages/*/*/*/*' diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index 168fb2deab3b3..0000000000000 --- a/.github/renovate.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": [ "config:base" ], - "labels": [ "[Type] Janitorial", "[Status] Needs Review" ], - "prHourlyLimit": 1, - "supportPolicy": [ "lts_latest" ], - "timezone": "UTC", - "schedule": [ "every weekend" ], - "updateNotScheduled": false, - "ignoreDeps": [ "mockery/mockery", "php-mock/php-mock", "phpunit/phpunit" ], - "packageRules": [ - { - "extends": "monorepo:wordpress", - "separateMajorMinor": false, - "prPriority": 1 - }, - { - "depTypeList": [ "monorepo:wordpress", "monorepo:react" ], - "groupName": "React and WordPress monorepos" - } - ] -} diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 1616317f350fe..0000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,63 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before a stale Issue or Pull Request is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: false - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 180 # 6 months - -# Issues with these labels will never be considered stale -exemptLabels: - - "[Pri] High" - - "[Pri] BLOCKER" - - "[Type] Good For Community" - - "[Type] Good First Bug" - - "FixTheFlows" - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: true - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: false - -# Label to use when marking an issue as stale -staleLabel: "[Status] Stale" - -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 2 - -# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': -issues: - # Comment to post when marking an issue as stale. Set to `false` to disable - markComment: > -

This issue has been marked as stale. This happened because:

- - - -

No further action is needed. But it's worth checking if this ticket has clear - reproduction steps and it is still reproducible. Feel free to close this issue - if you think it's not valid anymore — if you do, please add a brief - explanation.

- -pulls: - # Number of days of inactivity before an Issue or Pull Request becomes stale - daysUntilStale: 90 # 3 months - markComment: > -

This PR has been marked as stale. This happened because:

- - - -

No further action is needed. But it's worth checking if this PR has clear - testing instructions, is it up to date with master, and it is still valid. - Feel free to close this issue if you think it's not valid anymore — if you - do, please add a brief explanation.

diff --git a/.github/workflows/build-master.yml b/.github/workflows/build-master.yml deleted file mode 100644 index f6c1f43bb252f..0000000000000 --- a/.github/workflows/build-master.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Build production to master-built -on: - push: - branches: - - master - -jobs: - build_master_job: - runs-on: ubuntu-latest - name: Build production version of master to master-built branch! - steps: - - name: Checkout Jetpack - uses: actions/checkout@master - - name: Build production version - uses: automattic/action-jetpack-build-to-branch@master - with: - branch_pull: 'master' - branch_push: 'master-built' - commit_message: 'Automated production build from master' \ No newline at end of file diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml deleted file mode 100644 index dcafb97dc984a..0000000000000 --- a/.github/workflows/label.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: "Pull Request Labeler" -on: -- pull_request - -jobs: - triage: - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v2 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/needs-review.yml b/.github/workflows/needs-review.yml deleted file mode 100644 index 24c7755c9d510..0000000000000 --- a/.github/workflows/needs-review.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: "Needs Review Label Actions" -on: - push: - branches-ignore: - - '**' - #pull_request: - #types: [labeled] - -jobs: - comment: - runs-on: ubuntu-latest - name: Comment on Needs Review label - if: github.event.label.name == '[Status] Needs Review' - steps: - - name: Comment - uses: actions/github-script@0.8.0 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - github.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'Howdy! The Jetpack team has disappeared for a few days to a secret island lair to concoct new ways to make Jetpack one hundred billion percent better. As a result, your Pull Request may not be reviewed right away. Do not worry, we will be back next week to look at your work! Thank you for your understanding.' - }) diff --git a/.github/workflows/required-review.yml b/.github/workflows/required-review.yml deleted file mode 100644 index 371efff562811..0000000000000 --- a/.github/workflows/required-review.yml +++ /dev/null @@ -1,12 +0,0 @@ -on: pull_request_review -name: Check required reviews -jobs: - check_required_reviews: - name: Checking required reviews - runs-on: ubuntu-latest - steps: - - name: Check for required review approval - uses: automattic/action-required-review@master - env: - REQUIRED_REVIEW_TEAM_ID: "2787210" - GITHUB_TOKEN: ${{ secrets.API_TOKEN_GITHUB }} diff --git a/.github/workflows/update-package.yml b/.github/workflows/update-package.yml deleted file mode 100644 index f38bae204aeb2..0000000000000 --- a/.github/workflows/update-package.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Update a package when it is updated in the monorepo -on: - push: - branches: - - master # Every time a PR is merged to master. - paths: - - 'packages/**' # Only when package files are modified. - -jobs: - update_package: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Keep Jetpack Packages up to date - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} - run: | - sh ./bin/update-package.sh diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ebe3ee137fa44..0000000000000 --- a/.gitignore +++ /dev/null @@ -1,64 +0,0 @@ -## FOLDERS -/.sass-cache/ -/node_modules -vendor/bin -vendor/dealerdirect -vendor/phpcompatibility -vendor/sirbrillig -vendor/squizlabs -vendor/wp-coding-standards -vendor/automattic/jetpack-autoloader -/wpcom-test-backup/ -/.vscode/ -/logs - - -## FILES -.DS_Store -*.code-workspace -# Custom environment for docker-compose (used by docker-compose.yml) -/.env -/docker/compose-extras.yml -languages/messages.pot -.idea -*.iml -npm-debug.log -tests/php/files/jetpack-150x150.jpg -*.unison.tmp -.eslintcache -yarn-error.log -.phpcs.xml -phpcs.xml -/modules/**/*.min.css.map - -## Things for docker-composer -/docker/data/mysql/* -!/docker/data/mysql/.gitkeep -/docker/logs/* -!/docker/logs/.gitkeep -# Custom environment for docker containers -/docker/.env -/docker/wordpress-develop/* -!/docker/wordpress-develop/.gitkeep -/docker/wordpress/* - -!/docker/wordpress/.gitkeep - -# Don't list files below this comment if they need to be ignored for bundling the Jetpack release. -# From here on, we only list files that are generated by a build process, ignored in git, but should be present in SVN. -# -/_inc/client -/docker/ -__snapshots__/ -/extensions/**/*.css -/extensions/**/*.gif -/extensions/**/*.jpeg -/extensions/**/*.jpg -/extensions/**/*.js -/extensions/**/*.json -/extensions/**/*.jsx -/extensions/**/*.md -/extensions/**/*.png -/extensions/**/*.sass -/extensions/**/*.scss -/extensions/**/*.svg diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 1dab4ed4c3020..0000000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -save-exact = true diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index f599e28b8ab0d..0000000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -10 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ce59d627e38ca..0000000000000 --- a/.travis.yml +++ /dev/null @@ -1,158 +0,0 @@ -# Travis CI Configuration File - -# Tell Travis CI which distro to use -dist: trusty - -# Tell Travis CI we're using PHP -language: php - -services: - - docker - -# Run Matrix for these PHP versions -php: -- "5.6" -- "7.0" -- "7.2" -- "7.3" -- "7.4snapshot" - -env: - # Global variable is re-defined in matrix-include -list - global: - - WP_TRAVISCI=phpunit - # Run phpunit in Travis matrix for these combinations - matrix: - - WP_BRANCH=master SIMPLE_AND_MULTISITE=1 # SIMPLE_AND_MULTISITE is just a way to explicitly mention that we run both suites - - WP_BRANCH=latest - - WP_BRANCH=previous PHP_LINT=1 - -# Define a matrix of additional build configurations -# The versions listed above will automatically create our first configuration, -# so it doesn't need to be re-defined below. -matrix: - fast_finish: true - include: - - if: branch !~ /(^branch-.*-built)/ - name: "JavaScript & CSS lint" - language: node_js - env: WP_TRAVISCI="yarn lint" - - if: branch !~ /(^branch-.*-built)/ - name: "Danger CI, test dashboard & extensions" - language: node_js - env: WP_TRAVISCI="yarn test-dangerci-and-adminpage-and-extensions" - - if: branch !~ /(^branch-.*-built)/ - name: "Build dashboard & extensions" - language: node_js - env: WP_TRAVISCI="yarn build" - - # Disable for now until we fix all the spelling issues - # - name: "Spell check Markdown files" - # if: branch !~ /(^branch-.*-built)/ - # language: node_js - # env: WP_TRAVISCI="yarn test-spelling" - - - php: "nightly" - name: "PHP Nightly" - - # Code Climate code coverage. - - php: "7.3" - name: "Code Coverage" - env: - - WP_BRANCH=previous - - DO_COVERAGE=true - before_script: - - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - - chmod +x ./cc-test-reporter - - ./cc-test-reporter before-build - - export PATH="$HOME/.composer/vendor/bin:$PATH" - - export PLUGIN_SLUG=$(basename $(pwd)) - - ./tests/setup-travis.sh - after_script: - - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT -t clover -p /tmp/wordpress-previous/src/wp-content/plugins/jetpack - - - php: "7.0" - name: "E2E tests" - # Since build secrets are not available for forks, we would not be able to (and don't want) decrypt the config file - # so we just skip the E2E job all together - if: branch !~ /(^branch-.*-built)/ AND env(RUN_E2E) = true AND fork = false - before_script: - # Enable user namespace cloning - - "sysctl kernel.unprivileged_userns_clone=1" - - nvm install - - yarn && yarn build-production - - ./tests/e2e/bin/setup-e2e-travis.sh - - yarn test-decrypt-config - script: - - yarn test-e2e --runInBand --verbose - after_script: - - sudo apt-add-repository -y ppa:qameta/allure - - sudo apt-get update - - sudo apt-get -y install allure - - ./tests/e2e/bin/push-allure-artifacts.sh - addons: - apt: - packages: - - nginx - # This is required to run new chrome on old trusty - - libnss3 - - php: "7.0" - name: "Legacy full sync" - env: LEGACY_FULL_SYNC=1 WP_BRANCH=latest - - allow_failures: - - name: "PHP Nightly" - - name: "Spell check Markdown files" - - name: "Code Coverage" - - name: "E2E tests" - -cache: - directories: - - $HOME/.composer/cache/files - - $HOME/.cache/yarn - - $HOME/.phpbrew - -# whitelist branches for the "push" build check -branches: - only: - - master - - master-stable - - /^branch-.*$/ - - /^feature\/.*$/ - -# Git clone depth -# By default Travis CI clones repositories to a depth of 50 commits -git: - depth: 1 - -before_script: - - export PLUGIN_SLUG=$(basename $(pwd)) - - export PATH="$HOME/.composer/vendor/bin:$PATH" - - ./tests/setup-travis.sh - -script: ./tests/run-travis.sh - -sudo: false - -notifications: - webhooks: - urls: - - https://betadownload.jetpack.me/travis.php - on_success: always # Beta builder needs notifications for successful builds - email: - on_success: never # default: change - recipients: - - enej.bajgoric@automattic.com - - georgestephanis@automattic.com - - miguel@automattic.com - - rocco@automattic.com - - smart@automattic.com - - eric.binnion@automattic.com - - allendav@automattic.com - - beau@automattic.com - # Encrypted Slack notification address - - secure: "WQdTdmYuifSW0hiJGXpQGKystMASC50QvxHlyUL5SM3h5GP8aCgeSsHuXvKPe3dT3Pffhk0dSHBfDtdWFwSHW/upURhg0vs4dm7+nxxvGZiTPzKcuAIjgvCoqWM7teyda/XqFGNSnv+XsT34uoyPhhFgd45T3oS+QQ3aNCruFak=" - -addons: - code_climate: - repo_token: 683bd559e5214ca3b721092af177893f05765ba90d2589fcf35d7e85c6ea01e8 diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index e82774c1bd5d4..0000000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,357 +0,0 @@ -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - - -=================================== - - -GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - -Copyright (C) 1989, 1991 Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - -To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - -We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - -Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - -Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - -The precise terms and conditions for copying, distribution and -modification follow. - -GNU GENERAL PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - -1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - -2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - -a) You must cause the modified files to carry prominent notices -stating that you changed the files and the date of any change. - -b) You must cause any work that you distribute or publish, that in -whole or in part contains or is derived from the Program or any -part thereof, to be licensed as a whole at no charge to all third -parties under the terms of this License. - -c) If the modified program normally reads commands interactively -when run, you must cause it, when started running for such -interactive use in the most ordinary way, to print or display an -announcement including an appropriate copyright notice and a -notice that there is no warranty (or else, saying that you provide -a warranty) and that users may redistribute the program under -these conditions, and telling the user how to view a copy of this -License. (Exception: if the Program itself is interactive but -does not normally print such an announcement, your work based on -the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - -3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - -a) Accompany it with the complete corresponding machine-readable -source code, which must be distributed under the terms of Sections -1 and 2 above on a medium customarily used for software interchange; or, - -b) Accompany it with a written offer, valid for at least three -years, to give any third party, for a charge no more than your -cost of physically performing source distribution, a complete -machine-readable copy of the corresponding source code, to be -distributed under the terms of Sections 1 and 2 above on a medium -customarily used for software interchange; or, - -c) Accompany it with the information you received as to the offer -to distribute corresponding source code. (This alternative is -allowed only for noncommercial distribution and only if you -received the program in object code or executable form with such -an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - -4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - -5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - -6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - -7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - -8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - -9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - -10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - -11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - -12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - -Copyright (C) - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - -Gnomovision version 69, Copyright (C) year name of author -Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - -Yoyodyne, Inc., hereby disclaims all copyright interest in the program -`Gnomovision' (which makes passes at compilers) written by James Hacker. - -, 1 April 1989 -Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/_inc/.eslintrc.js b/_inc/.eslintrc.js deleted file mode 100644 index a8e05fc203023..0000000000000 --- a/_inc/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - // Use ESlint from modules folder. JS files here are not transpiled unless otherwise configured. - root: true, - extends: [ '../modules/.eslintrc.js' ], -}; diff --git a/_inc/client/.eslintrc.js b/_inc/client/.eslintrc.js deleted file mode 100644 index d58d0d045ebac..0000000000000 --- a/_inc/client/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - // Use root level ESlint configuration. - // JavaScript files inside this folder are meant to be transpiled by Webpack. - root: true, - extends: [ '../../.eslintrc.js' ], -}; diff --git a/_inc/client/README.md b/_inc/client/README.md deleted file mode 100644 index 8616be72ce10f..0000000000000 --- a/_inc/client/README.md +++ /dev/null @@ -1,186 +0,0 @@ - -## Jetpack React Admin UI - -The **Jetpack Admin Page** is a Javascript app built on **React**, [redux](https://github.com/reactjs/redux) and the fetch API. - -It's rendered on page load when visiting Jetpack's Admin Pages and fetches data from Jetpack via a REST API. - -### Data approach on the Admin Page - -The **Admin Page** uses **redux**, [redux-thunk](https://github.com/gaearon/redux-thunk), and [react-redux](https://github.com/reactjs/react-redux) for state handling trying to ressemble [Calypso's data approach, third Era](https://github.com/Automattic/wp-calypso/blob/master/docs/our-approach-to-data.md#third-era-redux-global-state-tree-december-2015---present). - -#### State related code - -The `_inc/client/state` directory holds directories named after things we fetch from the API. These directories hold the **redux**-related code like **state selectors**, **action types**, **action creators thunks** and **reducers**. - -#### Data fetching - -The data is being made available to the **Admin Page** by means of the WordPress REST API infrastructure present in WordPress since [version 4.4](https://make.wordpress.org/core/2015/10/28/rest-api-welcome-the-infrastructure-to-core/). - -Jetpack extends the Core API adding some specific methods for several jetpack-related actions exclusively, like activating or deactivating jetpack modules or updating any of the modules options. - -You may find additional reference for the Jetpack's HTTP API on the [rest-api.md](../../docs/rest-api.md) file. - -##### REST API Authentication - -The API requests rely on [cookie-based authentication and a specific nonce](http://v2.wp-api.org/guide/authentication/#cookie-authentication) -for requests to be authorized. - -The nonce is being served on the Jetpack admin page by usage of the [wp_localize_script](https://codex.wordpress.org/Function_Reference/wp_localize_script) mechanism for passing values from PHP code to the JS scope. - -This nonce is created with the action `wp_rest`. - -The nonce and the API root URL are made available on - -``` -window.Initial_State.WP_API_nonce; -window.Initial_State.WP_API_root; -``` - -##### Query Components - -We rely extensively in [query components](https://github.com/Automattic/wp-calypso/blob/master/docs/our-approach-to-data.md#query-components) to declare the data needs from inside state-aware React components. - -These components dispatch the API-fetching actions creators that eventually feed the redux state reducers with data. - -#### State selectors - -We kept [state selectors](https://github.com/Automattic/wp-calypso/blob/master/docs/our-approach-to-data.md#selectors) definition inside the same file that defines the reducers for each leaf of the state tree. - -This was done this way to keep functions that are aware of the tree shape on the same file while we were building the **Admin Page** and learning this pattern altogether. - -Below, under [Internal API](#internal-api) you'll find a brief listing about available state selectors you may need in the process of writing a new React component which will be connected to the Redux state tree. - -#### Action creators - -Every action creator defined in the **Admin Page** returns a Promise and is built as a thunk for handling [Asynchronous actions](https://github.com/reactjs/redux/blob/master/docs/advanced/AsyncActions.md#async-action-creators). - -### Internationalization of the Admin Page - -The **Admin Page** takes advantage of [i18n-calypso]() for internationalization purposes. - -Internally we use the `translate` function exported by `i18n-calypso` by aliasing to `__()`: - -``` -import { translate as __ } from 'i18n-calypso'; -... -
{ __( 'String' ) }
-``` - -### Browser compatibility of the Admin Page - - -#### Suport for non-javascript environments - -Some static HTML is generated from the JSX files and rendered on build time before a release to provide a non-javascript UI with basic functionality if the browser does not report javascript capabilities. - -## Internal API - -### Action types - -Action types dispatched during the UI lifecycle are listed in `state/action-types.js`. - -### Available state selectors - -* **getActiveStatsTab( state )** -* **getAdminEmailAddress( state )** -* **getAkismetData( state )** -* **getApiNonce( state )** -* **getApiRootUrl( state )** -* **getConnectUrl( state )** -* **getCurrentVersion( state )** -* **getInitialStateStatsData( state )** -* **getJetpackNotices( state )** -* **getJetpackStateNoticesErrorCode( state )** -* **getJetpackStateNoticesErrorDescription( state )** -* **getJetpackStateNoticesMessageCode( state )** -* **getLastDownTime( state )** -* **getModule( state, name )** -* **getModuleOption( state, module_slug, option_name )** { -* **getModuleOptionValidValues( state, module_slug, option_name )** -* **getModules( state )** -* **getModulesByFeature( state,**eature ) { -* **getModulesThatRequireConnection( state )** -* **getPluginUpdates( state )** -* **getProtectCount( state )** -* **getSearchTerm( state )** -* **getSettings( state )** -* **getSiteAdminUrl( state )** -* **getSiteConnectionStatus( state )** -* **getSiteDevMode( state )** -* **getSitePlan( state )** -* **getSiteRawUrl( state )** -* **getSiteRoles( state )** -* **getStatsData( state )** -* **getTracksUserData( state )** -* **getUserWpComAvatar( state )** -* **getUserWpComEmail( state )** -* **getUserWpComLogin( state )** -* **getUsername( state )** -* **isGutenbergAvailable( state )** - -### Available action creators (thunks) - -* **activateModule( slug )** -* **deactivateModule( slug )** -* **disconnectSite()** -* **dismissJetpackActionNotice( notice )** -* **dismissJetpackNotice( notice )** -* **fetchAkismetData()** -* **fetchConnectUrl()** -* **fetchLastDownTime()** -* **fetchModule()** -* **fetchModules()** -* **fetchPluginUpdates()** -* **fetchPluginsData()** -* **fetchProtectCount()** -* **fetchSettings()** -* **fetchSiteConnectionStatus()** -* **fetchSiteData()** -* **fetchStatsData( range )** -* **fetchUserConnectionData()** -* **filterSearch( term )** -* **regeneratePostByEmailAddress()** -* **resetOptions( options )** -* **setInitialState()** -* **statsSwitchTab( tab )** -* **unlinkUser()** -* **updateModuleOptions( slug, newOptionValues )** -* **updateSetting( updatedOption )** - -#### How to use selectors and actions creators from a component file - -```javascript -import { getModules, isModuleActivated, activateModule } from 'state/modules'; - -export const YourComponent = ( props ) => ( -
- -
-) - -// Connect selectors to the component's props -const mapStateToProps = ( state, ownProps ) => { - return { - modules: getModules( state ), - isModuleActivated: isModuleActivated( state, 'protect' ), - ...ownProps - } -} - -// Connect action creators to the component's props -const mapDispatchToProps = ( dispatch ) => { - return { - activate: ( module_name ) => dispatch( activateModule( module_name ) ) - deactivate: ( module_name ) => dispatch( deactivateModule( module_name ) ) - } -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)( YourComponent ) -``` diff --git a/_inc/client/admin.js b/_inc/client/admin.js deleted file mode 100644 index 177ea1a189f7d..0000000000000 --- a/_inc/client/admin.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * External dependencies - */ -import ReactDOM from 'react-dom'; -import React from 'react'; -import { Provider } from 'react-redux'; -import { HashRouter, Route, Switch } from 'react-router-dom'; -import { assign, get } from 'lodash'; - -/** - * Internal dependencies - */ -import accessibleFocus from 'lib/accessible-focus'; -import store from 'state/redux-store'; -import i18n from 'i18n-calypso'; -import Main from 'main'; -import * as actionTypes from 'state/action-types'; - -// Initialize the accessibile focus to allow styling specifically for keyboard navigation -accessibleFocus(); - -const Initial_State = window.Initial_State; - -Initial_State.locale = JSON.parse( Initial_State.locale ); -Initial_State.locale = get( Initial_State.locale, [ 'locale_data', 'jetpack' ], {} ); - -if ( 'undefined' !== typeof Initial_State.locale[ '' ] ) { - Initial_State.locale[ '' ].localeSlug = Initial_State.localeSlug; - - // Overloading the toLocaleString method to use the set locale - Number.prototype.realToLocaleString = Number.prototype.toLocaleString; - - Number.prototype.toLocaleString = function( locale, options ) { - locale = locale || Initial_State.localeSlug; - options = options || {}; - - return this.realToLocaleString( locale, options ); - }; -} else { - Initial_State.locale = { '': { localeSlug: Initial_State.localeSlug } }; -} - -i18n.setLocale( Initial_State.locale ); - -// Add dispatch and actionTypes to the window object so we can use it from the browser's console -if ( 'undefined' !== typeof window && process.env.NODE_ENV === 'development' ) { - assign( window, { - actionTypes: actionTypes, - dispatch: store.dispatch, - } ); -} - -render(); - -/** - * - */ -function render() { - const container = document.getElementById( 'jp-plugin-container' ); - - if ( container === null ) { - return; - } - - ReactDOM.render( -
- - - - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - - - -
- - - - -
, - container - ); -} - -/** - * Get translated route name according to route path - * - * @param {string} path - route path - * @returns {string} translated route name - */ -export function getRouteName( path ) { - switch ( path ) { - case '/dashboard': - return i18n.translate( 'At A Glance', { context: 'Navigation item.' } ); - case '/setup': - return i18n.translate( 'Set up', { context: 'Navigation item.' } ); - case '/my-plan': - return i18n.translate( 'My Plan', { context: 'Navigation item.' } ); - case '/plans': - return i18n.translate( 'Plans', { context: 'Navigation item.' } ); - case '/plans-prompt': - return i18n.translate( 'Plans', { context: 'Navigation item.' } ); - case '/settings': - return i18n.translate( 'Settings', { context: 'Navigation item.' } ); - case '/discussion': - return i18n.translate( 'Discussion', { context: 'Navigation item.' } ); - case '/security': - return i18n.translate( 'Security', { context: 'Navigation item.' } ); - case '/performance': - return i18n.translate( 'Performance', { context: 'Navigation item.' } ); - case '/traffic': - return i18n.translate( 'Traffic', { context: 'Navigation item.' } ); - case '/writing': - return i18n.translate( 'Writing', { context: 'Navigation item.' } ); - case '/sharing': - return i18n.translate( 'Sharing', { context: 'Navigation item.' } ); - default: - return i18n.translate( 'At A Glance', { context: 'Navigation item.' } ); - } -} diff --git a/_inc/client/at-a-glance/activity.jsx b/_inc/client/at-a-glance/activity.jsx deleted file mode 100644 index f6bf7102de0b3..0000000000000 --- a/_inc/client/at-a-glance/activity.jsx +++ /dev/null @@ -1,86 +0,0 @@ -/** - * External dependencies - */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import DashItem from 'components/dash-item'; -import Card from 'components/card'; -import { translate as __ } from 'i18n-calypso'; -// import { get, includes } from 'lodash'; -import classNames from 'classnames'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import { getSitePlan } from 'state/site'; -import { isDevMode } from 'state/connection'; -//import { PLAN_JETPACK_BUSINESS, PLAN_JETPACK_BUSINESS_MONTHLY, PLAN_VIP } from 'lib/plans/constants'; - -class DashActivity extends Component { - static propTypes = { - inDevMode: PropTypes.bool.isRequired, - siteRawUrl: PropTypes.string.isRequired, - sitePlan: PropTypes.object.isRequired, - }; - - static defaultProps = { - inDevMode: false, - siteRawUrl: '', - sitePlan: '', - }; - - render() { - const { inDevMode } = this.props; - // const sitePlan = get( this.props.sitePlan, 'product_slug', 'jetpack_free' ); - // const hasBackups = includes( [ PLAN_JETPACK_BUSINESS, PLAN_JETPACK_BUSINESS_MONTHLY, PLAN_VIP ], sitePlan ); - // const maybeUpgrade = hasBackups - // ? __( "{{a}}View your site's activity{{/a}} in a single feed where you can see when events occur and rewind them if you need to.", { - // components: { - // a: activityLogLink - // } - // } ) - // : __( "{{a}}View your site's activity{{/a}} in a single feed where you can see when events occur and, {{plan}}with a plan{{/plan}}, rewind them if you need to.", { - // components: { - // a: activityLogLink, - // plan: - // } - // } ); - - // @todo: update this to use rewind text/CTA when available - const activityLogOnlyText = __( - 'Jetpack keeps a complete record of everything that happens on your site, taking the guesswork out of site management, debugging, and repair.' - ); - - return ( -
- -

- { inDevMode ? __( 'Unavailable in Dev Mode.' ) : activityLogOnlyText } -

-
- - { __( 'View site activity' ) } - -
- ); - } -} - -export default connect( state => ( { - sitePlan: getSitePlan( state ), - inDevMode: isDevMode( state ), -} ) )( DashActivity ); diff --git a/_inc/client/at-a-glance/akismet.jsx b/_inc/client/at-a-glance/akismet.jsx deleted file mode 100644 index 224dbd13eebe8..0000000000000 --- a/_inc/client/at-a-glance/akismet.jsx +++ /dev/null @@ -1,214 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { numberFormat, translate as __ } from 'i18n-calypso'; -import { get } from 'lodash'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import analytics from 'lib/analytics'; -import { PLAN_JETPACK_PREMIUM } from 'lib/plans/constants'; -import Card from 'components/card'; -import DashItem from 'components/dash-item'; -import restApi from 'rest-api'; -import QueryAkismetData from 'components/data/query-akismet-data'; -import { getAkismetData } from 'state/at-a-glance'; -import { getSitePlan } from 'state/site'; -import { isDevMode } from 'state/connection'; -import { getApiNonce, getUpgradeUrl } from 'state/initial-state'; -import JetpackBanner from 'components/jetpack-banner'; - -class DashAkismet extends Component { - static propTypes = { - siteRawUrl: PropTypes.string.isRequired, - siteAdminUrl: PropTypes.string.isRequired, - - // Connected props - akismetData: PropTypes.oneOfType( [ PropTypes.string, PropTypes.object ] ).isRequired, - isDevMode: PropTypes.bool.isRequired, - upgradeUrl: PropTypes.string.isRequired, - }; - - static defaultProps = { - siteRawUrl: '', - siteAdminUrl: '', - akismetData: 'N/A', - isDevMode: '', - }; - - trackActivateClick() { - analytics.tracks.recordJetpackClick( { - type: 'activate-link', - target: 'at-a-glance', - feature: 'anti-spam', - } ); - } - - onActivateClick = () => { - this.trackActivateClick(); - - restApi.activateAkismet().then( () => { - window.location.href = this.props.siteAdminUrl + 'admin.php?page=akismet-key-config'; - } ); - - return false; - }; - - getContent() { - const akismetData = this.props.akismetData; - const labelName = __( 'Anti-spam' ); - const isSiteOnFreePlan = - 'jetpack_free' === get( this.props.sitePlan, 'product_slug', 'jetpack_free' ); - - const support = { - text: __( - 'Jetpack Anti-spam powered by Akismet. Comments and contact form submissions are checked against our global database of spam.' - ), - link: 'https://akismet.com/', - privacyLink: 'https://automattic.com/privacy/', - }; - - const getAkismetUpgradeBanner = () => { - const description = __( 'Already have a key? {{a}}Activate Akismet{{/a}}', { - components: { - a:
, - }, - } ); - - return ( - - ); - }; - - if ( 'N/A' === akismetData ) { - return ( - -

{ __( 'Loading…' ) }

-
- ); - } - - const hasSitePlan = false !== this.props.sitePlan; - - if ( isSiteOnFreePlan ) { - if ( 'not_installed' === akismetData ) { - return ( - - ); - } - - if ( 'not_active' === akismetData ) { - return ( - - ); - } - - if ( 'invalid_key' === akismetData ) { - return ( - - ); - } - } - - if ( [ 'not_installed', 'not_active', 'invalid_key' ].includes( akismetData ) ) { - return ( - - { __( - "Your Jetpack plan provides anti-spam protection through Akismet. Click 'set up' to enable it on your site." - ) } - - ); - } - - return [ - -

{ numberFormat( akismetData.all.spam ) }

-

- { __( 'Spam comments blocked.', { - context: 'Example: "412 Spam comments blocked"', - } ) } -

-
, - ! this.props.isDevMode && ( - - { __( 'Moderate comments' ) } - - ), - ]; - } - - render() { - return ( -
- - { this.getContent() } -
- ); - } -} - -export default connect( state => ( { - akismetData: getAkismetData( state ), - sitePlan: getSitePlan( state ), - isDevMode: isDevMode( state ), - upgradeUrl: getUpgradeUrl( state, 'aag-akismet' ), - nonce: getApiNonce( state ), -} ) )( DashAkismet ); diff --git a/_inc/client/at-a-glance/backups.jsx b/_inc/client/at-a-glance/backups.jsx deleted file mode 100644 index 2db7309571b33..0000000000000 --- a/_inc/client/at-a-glance/backups.jsx +++ /dev/null @@ -1,255 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import DashItem from 'components/dash-item'; -import { translate as __ } from 'i18n-calypso'; -import { get, isEmpty, noop } from 'lodash'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import Card from 'components/card'; -import JetpackBanner from 'components/jetpack-banner'; -import QueryVaultPressData from 'components/data/query-vaultpress-data'; -import { getPlanClass, PLAN_JETPACK_PREMIUM } from 'lib/plans/constants'; -import { getSitePlan } from 'state/site'; -import { isPluginInstalled } from 'state/site/plugins'; -import { getVaultPressData } from 'state/at-a-glance'; -import { isDevMode } from 'state/connection'; -import { getUpgradeUrl, showBackups } from 'state/initial-state'; - -/** - * Displays a card for Backups based on the props given. - * - * @param {object} props Settings to render the card. - * @returns {object} Backups card - */ -const renderCard = props => ( - -

{ props.content }

-
-); - -class DashBackups extends Component { - static propTypes = { - siteRawUrl: PropTypes.string.isRequired, - getOptionValue: PropTypes.func.isRequired, - rewindStatus: PropTypes.string.isRequired, - - // Connected props - vaultPressData: PropTypes.any.isRequired, - sitePlan: PropTypes.object.isRequired, - isDevMode: PropTypes.bool.isRequired, - isVaultPressInstalled: PropTypes.bool.isRequired, - upgradeUrl: PropTypes.string.isRequired, - }; - - static defaultProps = { - siteRawUrl: '', - getOptionValue: noop, - vaultPressData: '', - sitePlan: '', - isDevMode: false, - isVaultPressInstalled: false, - rewindStatus: '', - }; - - getVPContent() { - const { - sitePlan, - isVaultPressInstalled, - getOptionValue, - siteRawUrl, - vaultPressData, - } = this.props; - - if ( getOptionValue( 'vaultpress' ) && 'success' === get( vaultPressData, 'code', '' ) ) { - return renderCard( { - className: 'jp-dash-item__is-active', - status: 'is-working', - content: ( - - { get( vaultPressData, 'message', '' ) } -   - { __( '{{a}}View backup details{{/a}}.', { - components: { - a: ( -
- ), - }, - } ) } - - ), - } ); - } - - if ( ! isEmpty( sitePlan ) ) { - // If site has a paid plan - if ( 'jetpack_free' !== get( sitePlan, 'product_slug', 'jetpack_free' ) ) { - return renderCard( { - className: 'jp-dash-item__is-inactive', - status: isVaultPressInstalled ? 'pro-inactive' : 'pro-uninstalled', - content: __( - 'To automatically back up your entire site, please {{a}}install and activate{{/a}} VaultPress.', - { - components: { - a: ( - - ), - }, - } - ), - } ); - } - - return renderCard( { - className: 'jp-dash-item__is-inactive', - status: 'no-pro-uninstalled-or-inactive', - overrideContent: ( - - ), - } ); - } - - return renderCard( { - className: '', - status: '', - content: __( 'Loading…' ), - } ); - } - - getRewindContent() { - const { planClass, rewindStatus, siteRawUrl } = this.props; - const buildAction = ( url, message ) => ( - - { message } - - ); - const buildCard = message => - renderCard( { - className: 'jp-dash-item__is-active', - status: 'is-working', - feature: 'rewind', - content: message, - } ); - - switch ( rewindStatus ) { - case 'provisioning': - return ( - - { buildCard( __( "We are configuring your site's backups." ) ) } - - ); - case 'awaiting_credentials': - return ( - - { buildCard( - __( "You need to enter your server's credentials to finish the setup." ) - ) } - { buildAction( - getRedirectUrl( 'calypso-settings-security', { site: siteRawUrl } ), - __( 'Enter credentials' ) - ) } - - ); - case 'active': - const message = [ 'is-business-plan', 'is-realtime-backup-plan' ].includes( planClass ) - ? __( 'We are backing up your site in real-time.' ) - : __( 'We are backing up your site daily.' ); - - return ( - - { buildCard( message ) } - { buildAction( - getRedirectUrl( 'calypso-activity-log', { site: siteRawUrl, query: 'group=rewind' } ), - __( "View your site's backups" ) - ) } - - ); - } - - return false; - } - - render() { - if ( ! this.props.showBackups ) { - return null; - } - - if ( this.props.isDevMode ) { - return ( -
- { renderCard( { - className: 'jp-dash-item__is-inactive', - status: 'no-pro-uninstalled-or-inactive', - content: __( 'Unavailable in Dev Mode.' ), - } ) } -
- ); - } - - return ( -
- - { 'unavailable' === this.props.rewindStatus ? ( - this.getVPContent() - ) : ( -
{ this.getRewindContent() }
- ) } -
- ); - } -} - -export default connect( state => { - const sitePlan = getSitePlan( state ); - - return { - vaultPressData: getVaultPressData( state ), - sitePlan, - planClass: getPlanClass( sitePlan ), - isDevMode: isDevMode( state ), - isVaultPressInstalled: isPluginInstalled( state, 'vaultpress/vaultpress.php' ), - showBackups: showBackups( state ), - upgradeUrl: getUpgradeUrl( state, 'aag-backups' ), - }; -} )( DashBackups ); diff --git a/_inc/client/at-a-glance/connections.jsx b/_inc/client/at-a-glance/connections.jsx deleted file mode 100644 index ab0ebefc32b59..0000000000000 --- a/_inc/client/at-a-glance/connections.jsx +++ /dev/null @@ -1,228 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { translate as __ } from 'i18n-calypso'; -import Gridicon from 'components/gridicon'; -import DashItem from 'components/dash-item'; - -/** - * Internal dependencies - */ -import { getSiteConnectionStatus, isCurrentUserLinked, isDevMode } from 'state/connection'; -import { - userCanDisconnectSite, - userIsMaster, - getUserWpComLogin, - getUserWpComEmail, - getUserWpComAvatar, - getUserGravatar, - getUsername, - getSiteIcon, -} from 'state/initial-state'; -import QueryUserConnectionData from 'components/data/query-user-connection'; -import ConnectButton from 'components/connect-button'; -import MobileMagicLink from 'components/mobile-magic-link'; - -export class DashConnections extends Component { - /* - * Render a card for site connection. If it's connected, indicate if user is the connection owner. - * Show alternative message if site is in development mode. - * - * @returns {string} - */ - siteConnection() { - let cardContent = ''; - - if ( this.props.isDevMode ) { - cardContent = ( -
- { this.props.siteIcon ? ( - - ) : ( - - ) } -
- { __( - 'Your site is in Development Mode, so it can not be connected to WordPress.com.' - ) } -
-
- ); - } else if ( true === this.props.siteConnectionStatus ) { - cardContent = ( -
-
- { this.props.siteIcon ? ( - - ) : ( - - ) } -
- { __( 'Your site is connected to WordPress.com.' ) } - { this.props.userIsMaster && ( - -
- { __( 'You are the Jetpack owner.' ) } -
- ) } -
-
- { this.props.userCanDisconnectSite && ( -
- -
- ) } -
- ); - } - - return cardContent; - } - - /* - * Render a card for user linking. If it's connected, show the currently linked user. - * Show an alternative message if site is in Dev Mode. - * - * @returns {string} - */ - userConnection() { - const maybeShowLinkUnlinkBtn = this.props.userIsMaster ? null : ( - - ); - - let cardContent = ''; - - if ( this.props.isDevMode ) { - // return nothing if this is an account connection card - cardContent = ( -
- { this.props.userGravatar ? ( - gravatar - ) : ( - - ) } -
- { __( 'The site is in Development Mode, so you can not connect to WordPress.com.' ) } -
-
- ); - } else { - cardContent = this.props.isLinked ? ( -
-
- gravatar -
- { __( 'Connected as {{span}}%(username)s{{/span}}', { - args: { - username: this.props.userWpComLogin, - }, - components: { - span: , - }, - comment: '%(username) is the WordPress user login name.', - } ) } -
{ this.props.userWpComEmail }
-
-
-
{ maybeShowLinkUnlinkBtn }
- -
- ) : ( -
-
- { __( 'Link your account to WordPress.com to get the most out of Jetpack.' ) } -
-
{ maybeShowLinkUnlinkBtn }
-
- ); - } - - return cardContent; - } - - render() { - return ( -
- -
-
-
- - { this.siteConnection() } - -
-
-
-
- - { this.userConnection() } - -
-
-
-
- ); - } -} - -DashConnections.propTypes = { - siteConnectionStatus: PropTypes.any.isRequired, - isDevMode: PropTypes.bool.isRequired, - userCanDisconnectSite: PropTypes.bool.isRequired, - userIsMaster: PropTypes.bool.isRequired, - isLinked: PropTypes.bool.isRequired, - userWpComLogin: PropTypes.any.isRequired, - userWpComEmail: PropTypes.any.isRequired, - userWpComAvatar: PropTypes.any.isRequired, - userGravatar: PropTypes.any.isRequired, - username: PropTypes.any.isRequired, -}; - -export default connect( state => { - return { - siteConnectionStatus: getSiteConnectionStatus( state ), - isDevMode: isDevMode( state ), - userCanDisconnectSite: userCanDisconnectSite( state ), - userIsMaster: userIsMaster( state ), - userWpComLogin: getUserWpComLogin( state ), - userWpComEmail: getUserWpComEmail( state ), - userWpComAvatar: getUserWpComAvatar( state ), - userGravatar: getUserGravatar( state ), - username: getUsername( state ), - isLinked: isCurrentUserLinked( state ), - siteIcon: getSiteIcon( state ), - }; -} )( DashConnections ); diff --git a/_inc/client/at-a-glance/index.jsx b/_inc/client/at-a-glance/index.jsx deleted file mode 100644 index 46f1be784bd54..0000000000000 --- a/_inc/client/at-a-glance/index.jsx +++ /dev/null @@ -1,187 +0,0 @@ -/** - * External dependencies - */ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { translate as __ } from 'i18n-calypso'; -import analytics from 'lib/analytics'; -import { chunk, get } from 'lodash'; - -/** - * Internal dependencies - */ -import { withModuleSettingsFormHelpers } from 'components/module-settings/with-module-settings-form-helpers'; -import DashSectionHeader from 'components/dash-section-header'; -import DashActivity from './activity'; -import DashStats from './stats/index.jsx'; -import DashProtect from './protect'; -import DashMonitor from './monitor'; -import DashScan from './scan'; -import DashAkismet from './akismet'; -import DashBackups from './backups'; -import DashPluginUpdates from './plugins'; -import DashPhoton from './photon'; -import DashSearch from './search'; -import DashConnections from './connections'; -import QuerySitePlugins from 'components/data/query-site-plugins'; -import QuerySite from 'components/data/query-site'; -import { - isMultisite, - userCanManageModules, - userCanViewStats, - userIsSubscriber, -} from 'state/initial-state'; -import { isDevMode } from 'state/connection'; -import { getModuleOverride } from 'state/modules'; - -const renderPairs = layout => - layout.map( ( item, layoutIndex ) => [ - item.header, - chunk( item.cards, 2 ).map( ( [ left, right ], cardIndex ) => ( -
-
{ left }
-
{ right }
-
- ) ), - ] ); - -class AtAGlance extends Component { - trackSecurityClick = () => analytics.tracks.recordJetpackClick( 'aag_manage_security_wpcom' ); - - render() { - const settingsProps = { - updateOptions: this.props.updateOptions, - getOptionValue: this.props.getOptionValue, - isUpdating: this.props.isUpdating, - multisite: this.props.multisite, - }; - const urls = { - siteAdminUrl: this.props.siteAdminUrl, - siteRawUrl: this.props.siteRawUrl, - }; - const securityHeader = ( - - ); - const connections = ( -
- - -
- ); - // Status can be unavailable, active, provisioning, awaiting_credentials - const rewindStatus = get( this.props.rewindStatus, [ 'state' ], '' ); - const securityCards = []; - securityCards.push( ); - if ( ! this.props.multisite ) { - securityCards.push( - - ); - } - securityCards.push( ); - securityCards.push( ); - - if ( 'inactive' !== this.props.getModuleOverride( 'protect' ) ) { - securityCards.push( ); - } - if ( 'inactive' !== this.props.getModuleOverride( 'monitor' ) ) { - securityCards.push( ); - } - - // Maybe add the rewind card - 'active' === rewindStatus && - securityCards.unshift( - - ); - - // If user can manage modules, we're in an admin view, otherwise it's a non-admin view. - if ( this.props.userCanManageModules ) { - const pairs = [ - { - header: securityHeader, - cards: securityCards, - }, - ]; - - const performanceCards = []; - if ( 'inactive' !== this.props.getModuleOverride( 'photon' ) ) { - performanceCards.push( ); - } - if ( 'inactive' !== this.props.getModuleOverride( 'search' ) ) { - performanceCards.push( ); - } - if ( performanceCards.length ) { - pairs.push( { - header: , - cards: performanceCards, - } ); - } - - return ( -
- - - - { renderPairs( pairs ) } - { connections } -
- ); - } - - /* - * Non-admin zone... - */ - let stats = ''; - if ( this.props.userCanViewStats ) { - stats = ; - } - - let protect = ''; - if ( this.props.getOptionValue( 'protect' ) ) { - protect = ; - } - - return this.props.userIsSubscriber ? ( -
- { stats } - { connections } -
- ) : ( -
- { stats } - { // Site Security - this.props.getOptionValue( 'protect' ) && securityHeader } - { protect } - { connections } -
- ); - } // render -} - -export default connect( state => { - return { - userCanManageModules: userCanManageModules( state ), - userCanViewStats: userCanViewStats( state ), - userIsSubscriber: userIsSubscriber( state ), - isDevMode: isDevMode( state ), - getModuleOverride: module_name => getModuleOverride( state, module_name ), - multisite: isMultisite( state ), - }; -} )( withModuleSettingsFormHelpers( AtAGlance ) ); diff --git a/_inc/client/at-a-glance/monitor.jsx b/_inc/client/at-a-glance/monitor.jsx deleted file mode 100644 index 7b127e33a8d2f..0000000000000 --- a/_inc/client/at-a-glance/monitor.jsx +++ /dev/null @@ -1,86 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { translate as __ } from 'i18n-calypso'; -import analytics from 'lib/analytics'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import { isModuleAvailable } from 'state/modules'; -import { isDevMode } from 'state/connection'; -import DashItem from 'components/dash-item'; - -class DashMonitor extends Component { - static propTypes = { - isDevMode: PropTypes.bool.isRequired, - isModuleAvailable: PropTypes.bool.isRequired, - }; - - activateAndTrack = () => { - analytics.tracks.recordEvent( 'jetpack_wpa_module_toggle', { - module: 'monitor', - toggled: 'on', - } ); - - this.props.updateOptions( { monitor: true } ); - }; - - getContent() { - const labelName = __( 'Downtime monitor' ); - - const support = { - text: __( - 'Jetpack’s downtime monitor will continuously monitor your site, and alert you the moment that downtime is detected.' - ), - link: getRedirectUrl( 'jetpack-support-monitor' ), - }; - - if ( this.props.getOptionValue( 'monitor' ) ) { - return ( - -

- { __( - 'Jetpack is monitoring your site. If we think your site is down, you will receive an email.' - ) } -

-
- ); - } - - return ( - -

- { this.props.isDevMode - ? __( 'Unavailable in Dev Mode.' ) - : __( - '{{a}}Activate Monitor{{/a}} to receive email notifications if your site goes down.', - { - components: { - a: , - }, - } - ) } -

- - ); - } - - render() { - return this.props.isModuleAvailable && this.getContent(); - } -} - -export default connect( state => ( { - isDevMode: isDevMode( state ), - isModuleAvailable: isModuleAvailable( state, 'monitor' ), -} ) )( DashMonitor ); diff --git a/_inc/client/at-a-glance/photon.jsx b/_inc/client/at-a-glance/photon.jsx deleted file mode 100644 index bfeedc6907bfa..0000000000000 --- a/_inc/client/at-a-glance/photon.jsx +++ /dev/null @@ -1,78 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import DashItem from 'components/dash-item'; -import { translate as __ } from 'i18n-calypso'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import { isModuleAvailable } from 'state/modules'; -import { isDevMode } from 'state/connection'; - -class DashPhoton extends Component { - static propTypes = { - isDevMode: PropTypes.bool.isRequired, - isModuleAvailable: PropTypes.bool.isRequired, - }; - - activatePhoton = () => this.props.updateOptions( { photon: true } ); - - getContent() { - const labelName = __( 'Image Accelerator' ); - - const support = { - text: __( - 'Jetpack will optimize your images and serve them from the server location nearest to your visitors. Using our global content delivery network will boost the loading speed of your site.' - ), - link: getRedirectUrl( 'jetpack-support-photon' ), - }; - - if ( this.props.getOptionValue( 'photon' ) ) { - return ( - -

- { __( - "Jetpack is optimizing your image sizes and download speed using our fast global network of servers. This improves your site's performance on desktop and mobile devices." - ) } -

-
- ); - } - - return ( - -

- { this.props.isDevMode - ? __( 'Unavailable in Dev Mode' ) - : __( - "{{a}}Activate{{/a}} to optimize image sizes and load images from Jetpack's fast global network of servers. This improves your site's performance on desktop and mobile devices.", - { - components: { - a: , - }, - } - ) } -

- - ); - } - - render() { - return this.props.isModuleAvailable && this.getContent(); - } -} - -export default connect( state => ( { - isDevMode: isDevMode( state ), - isModuleAvailable: isModuleAvailable( state, 'photon' ), -} ) )( DashPhoton ); diff --git a/_inc/client/at-a-glance/plugins.jsx b/_inc/client/at-a-glance/plugins.jsx deleted file mode 100644 index c2dd120ec4a01..0000000000000 --- a/_inc/client/at-a-glance/plugins.jsx +++ /dev/null @@ -1,123 +0,0 @@ -/** - * External dependencies - * - * @format - */ - -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { translate as __ } from 'i18n-calypso'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import analytics from 'lib/analytics'; -import Card from 'components/card'; -import DashItem from 'components/dash-item'; -import QueryPluginUpdates from 'components/data/query-plugin-updates'; -import { getPluginUpdates } from 'state/at-a-glance'; -import { isDevMode } from 'state/connection'; - -class DashPluginUpdates extends Component { - static propTypes = { - isDevMode: PropTypes.bool.isRequired, - siteRawUrl: PropTypes.string.isRequired, - siteAdminUrl: PropTypes.string.isRequired, - pluginUpdates: PropTypes.any.isRequired, - }; - - trackManagePlugins() { - analytics.tracks.recordJetpackClick( { - type: 'link', - target: 'at-a-glance', - feature: 'manage-plugins', - } ); - } - - getContent() { - const labelName = __( 'Plugin Updates' ); - const pluginUpdates = this.props.pluginUpdates; - - const support = { - text: __( - 'Jetpack’s Plugin Updates allows you to choose which plugins update automatically.' - ), - link: getRedirectUrl( 'jetpack-support-site-management' ), - }; - - if ( 'N/A' === pluginUpdates ) { - return ( - - -

{ __( 'Loading…' ) }

-
- ); - } - - const updatesAvailable = 'updates-available' === pluginUpdates.code; - const managePluginsUrl = getRedirectUrl( 'calypso-plugins-manage', { - site: this.props.siteRawUrl, - } ); - const workingOrInactive = this.props.getOptionValue( 'manage' ) ? 'is-working' : 'is-inactive'; - - return [ - - { updatesAvailable && ( -

- { __( '%(number)s', '%(number)s', { - count: pluginUpdates.count, - args: { number: pluginUpdates.count }, - } ) } -

- ) } -

- { updatesAvailable - ? [ - __( 'Plugin needs updating.', 'Plugins need updating.', { - count: pluginUpdates.count, - } ) + ' ', - ! this.props.isDevMode && - __( '{{a}}Turn on plugin autoupdates.{{/a}}', { - components: { a: }, - } ), - ] - : __( 'All plugins are up-to-date. Awesome work!' ) } -

- , - ! this.props.isDevMode && ( - - { __( 'Manage your plugins' ) } - - ), - ]; - } - - render() { - return ( -
- - { this.getContent() } -
- ); - } -} - -export default connect( state => ( { - pluginUpdates: getPluginUpdates( state ), - isDevMode: isDevMode( state ), -} ) )( DashPluginUpdates ); diff --git a/_inc/client/at-a-glance/protect.jsx b/_inc/client/at-a-glance/protect.jsx deleted file mode 100644 index 1c4d001cab9d9..0000000000000 --- a/_inc/client/at-a-glance/protect.jsx +++ /dev/null @@ -1,107 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import DashItem from 'components/dash-item'; -import { numberFormat, translate as __ } from 'i18n-calypso'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import QueryProtectCount from 'components/data/query-dash-protect'; -import { isModuleAvailable } from 'state/modules'; -import { getProtectCount } from 'state/at-a-glance'; -import { isDevMode } from 'state/connection'; - -class DashProtect extends Component { - static propTypes = { - isDevMode: PropTypes.bool.isRequired, - protectCount: PropTypes.any.isRequired, - isModuleAvailable: PropTypes.bool.isRequired, - }; - - activateProtect = () => this.props.updateOptions( { protect: true } ); - - getContent() { - const labelName = __( 'Protect' ); - const support = { - text: __( 'Protects your site from traditional and distributed brute force login attacks.' ), - link: getRedirectUrl( 'jetpack-support-protect' ), - }; - - if ( this.props.getOptionValue( 'protect' ) ) { - const protectCount = this.props.protectCount; - - if ( false === protectCount || '0' === protectCount || 'N/A' === protectCount ) { - return ( - -
- -

- { __( - 'Jetpack is actively blocking malicious login attempts. Data will display here soon!' - ) } -

-
-
- ); - } - return ( - -

{ numberFormat( protectCount ) }

-

- { __( 'Total malicious attacks blocked on your site.' ) } -

-
- ); - } - - return ( - -

- { this.props.isDevMode - ? __( 'Unavailable in Dev Mode' ) - : __( - '{{a}}Activate Protect{{/a}} to keep your site protected from malicious sign in attempts.', - { - components: { - a: , - }, - } - ) } -

- - ); - } - - render() { - return ( - this.props.isModuleAvailable && ( -
- - { this.getContent() } -
- ) - ); - } -} - -export default connect( state => ( { - protectCount: getProtectCount( state ), - isDevMode: isDevMode( state ), - isModuleAvailable: isModuleAvailable( state, 'protect' ), -} ) )( DashProtect ); diff --git a/_inc/client/at-a-glance/scan.jsx b/_inc/client/at-a-glance/scan.jsx deleted file mode 100644 index b73aa9eda12a5..0000000000000 --- a/_inc/client/at-a-glance/scan.jsx +++ /dev/null @@ -1,320 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { numberFormat, translate as __ } from 'i18n-calypso'; - -/** - * Internal dependencies - */ -import Card from 'components/card'; -import QueryVaultPressData from 'components/data/query-vaultpress-data'; -import QueryScanStatus from 'components/data/query-scan-status'; -import { getSitePlan, isFetchingSiteData } from 'state/site'; -import { getScanStatus, isFetchingScanStatus } from 'state/scan'; -import { isPluginInstalled } from 'state/site/plugins'; -import { getVaultPressScanThreatCount, getVaultPressData } from 'state/at-a-glance'; -import { isDevMode } from 'state/connection'; -import DashItem from 'components/dash-item'; -import { get, isArray } from 'lodash'; -import { getUpgradeUrl, showBackups } from 'state/initial-state'; -import JetpackBanner from 'components/jetpack-banner'; -import { getPlanClass, PLAN_JETPACK_PREMIUM } from 'lib/plans/constants'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Displays a card for Security Scan based on the props given. - * - * @param {object} props Settings to render the card. - * @returns {object} Security Scan card - */ -const renderCard = props => ( - - { isArray( props.content ) ? ( - props.content - ) : ( -

{ props.content }

- ) } -
-); - -const renderAction = ( url, message ) => ( - - { message } - -); - -const renderActiveCard = message => { - return renderCard( { - className: 'jp-dash-item__is-active', - status: 'is-working', - content: message, - } ); -}; - -class DashScan extends Component { - static propTypes = { - siteRawUrl: PropTypes.string.isRequired, - - // Connected props - vaultPressData: PropTypes.any.isRequired, - scanThreats: PropTypes.any.isRequired, - sitePlan: PropTypes.object.isRequired, - isDevMode: PropTypes.bool.isRequired, - isVaultPressInstalled: PropTypes.bool.isRequired, - fetchingSiteData: PropTypes.bool.isRequired, - upgradeUrl: PropTypes.string.isRequired, - }; - - static defaultProps = { - siteRawUrl: '', - vaultPressData: '', - scanThreats: 0, - sitePlan: '', - isDevMode: false, - isVaultPressInstalled: false, - fetchingSiteData: false, - }; - - getVPContent() { - const { sitePlan, planClass, fetchingSiteData } = this.props; - const hasSitePlan = false !== sitePlan; - const vpData = this.props.vaultPressData; - const scanEnabled = get( vpData, [ 'data', 'features', 'security' ], false ); - - if ( this.props.getOptionValue( 'vaultpress' ) ) { - if ( 'N/A' === vpData ) { - return renderCard( { - status: '', - content: __( 'Loading…' ), - } ); - } - - if ( scanEnabled ) { - // Check for threats - const threats = this.props.scanThreats; - if ( threats !== 0 ) { - return this.renderThreatsFound( threats, getRedirectUrl( 'vaultpress-dashboard' ) ); - } - - // All good - if ( vpData.code === 'success' ) { - return renderCard( { - status: 'is-working', - content: __( "No threats found, you're good to go!" ), - } ); - } - } - } - - if ( fetchingSiteData ) { - return renderCard( { - status: '', - content: __( 'Loading…' ), - } ); - } - - const inactiveOrUninstalled = this.props.isVaultPressInstalled - ? 'pro-inactive' - : 'pro-uninstalled'; - const hasPremium = 'is-premium-plan' === planClass; - const hasBusiness = 'is-business-plan' === planClass; - - const scanContent = - hasPremium || hasBusiness || scanEnabled ? ( -

- { __( - 'For automated, comprehensive scanning of security threats, please {{a}}install and activate{{/a}} VaultPress.', - { - components: { - a: ( - - ), - }, - } - ) } -

- ) : null; - - const overrideContent = null === scanContent ? this.getUpgradeBanner() : null; - - return renderCard( { - className: 'jp-dash-item__is-inactive', - status: hasSitePlan ? inactiveOrUninstalled : 'no-pro-uninstalled-or-inactive', - content: [ scanContent ], - overrideContent, - } ); - } - - getUpgradeBanner() { - return ( - - ); - } - - renderThreatsFound( numberOfThreats, dashboardUrl ) { - const { siteRawUrl } = this.props; - return ( - <> - { renderActiveCard( [ -

{ numberFormat( numberOfThreats ) }

, -

- { __( - 'Security threat found. Please {{a}}fix it{{/a}} as soon as possible.', - 'Security threats found. Please {{a}}fix these{{/a}} as soon as possible.', - { - count: numberOfThreats, - components: { - a: , - }, - } - ) } -

, - ] ) } - { renderAction( dashboardUrl, __( 'View security scan details' ) ) } - - ); - } - - getRewindContent() { - const { scanStatus, siteRawUrl } = this.props; - - const scanDashboardUrl = getRedirectUrl( 'calypso-scanner', { - site: siteRawUrl, - } ); - - if ( Array.isArray( scanStatus.threats ) && scanStatus.threats.length > 0 ) { - return this.renderThreatsFound( scanStatus.threats.length, scanDashboardUrl ); - } - - if ( scanStatus.credentials && scanStatus.credentials.length === 0 ) { - return ( - <> - { renderActiveCard( - __( "You need to enter your server's credentials to finish the setup." ) - ) } - { renderAction( - getRedirectUrl( 'calypso-settings-security', { site: siteRawUrl } ), - __( 'Enter credentials' ) - ) } - - ); - } - - switch ( scanStatus.state ) { - case 'provisioning': - return <>{ renderActiveCard( __( 'We are configuring your site protection.' ) ) }; - case 'idle': - case 'scanning': - return ( - <> - { renderActiveCard( - __( - 'We are making sure your site stays free of security threats. ' + - 'You will be notified if we find one.' - ) - ) } - { renderAction( - getRedirectUrl( 'calypso-scanner', { site: siteRawUrl } ), - __( 'View security scan details' ) - ) } - - ); - } - - return false; - } - - getUpgradeContent() { - return renderCard( { - className: 'jp-dash-item__is-inactive', - overrideContent: this.getUpgradeBanner(), - } ); - } - - render() { - if ( ! this.props.showBackups ) { - return null; - } - - if ( this.props.isDevMode ) { - return renderCard( { - className: 'jp-dash-item__is-inactive', - content: __( 'Unavailable in Dev Mode.' ), - } ); - } - - // Show loading while we're getting props. - // Once we get them, test the Scan system and then VaultPress in order. - const { scanStatus, vaultPressData, fetchingScanStatus } = this.props; - let content = renderCard( { content: __( 'Loading…' ) } ); - if ( ! fetchingScanStatus && scanStatus.state && 'unavailable' !== scanStatus.state ) { - content =
{ this.getRewindContent() }
; - } else if ( get( vaultPressData, [ 'data', 'features', 'security' ], false ) ) { - content = this.getVPContent(); - } else if ( 'N/A' === vaultPressData && ! fetchingScanStatus ) { - content = this.getUpgradeContent(); - } - - return ( -
- - - { content } -
- ); - } -} - -export default connect( state => { - const sitePlan = getSitePlan( state ); - - return { - scanStatus: getScanStatus( state ), - fetchingScanStatus: isFetchingScanStatus( state ), - vaultPressData: getVaultPressData( state ), - scanThreats: getVaultPressScanThreatCount( state ), - sitePlan, - planClass: getPlanClass( get( sitePlan, 'product_slug', '' ) ), - isDevMode: isDevMode( state ), - isVaultPressInstalled: isPluginInstalled( state, 'vaultpress/vaultpress.php' ), - fetchingSiteData: isFetchingSiteData( state ), - showBackups: showBackups( state ), - upgradeUrl: getUpgradeUrl( state, 'aag-scan' ), - }; -} )( DashScan ); diff --git a/_inc/client/at-a-glance/search.jsx b/_inc/client/at-a-glance/search.jsx deleted file mode 100644 index 0ef5c13d89de7..0000000000000 --- a/_inc/client/at-a-glance/search.jsx +++ /dev/null @@ -1,181 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { translate as __ } from 'i18n-calypso'; -import { noop } from 'lodash'; -import { getPlanClass, PLAN_JETPACK_SEARCH } from 'lib/plans/constants'; -import { SEARCH_DESCRIPTION, SEARCH_CUSTOMIZE_CTA, SEARCH_SUPPORT } from 'plans/constants'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import analytics from 'lib/analytics'; -import DashItem from 'components/dash-item'; -import Card from 'components/card'; -import JetpackBanner from 'components/jetpack-banner'; -import { isDevMode } from 'state/connection'; -import { getSitePlan, hasActiveSearchPurchase, isFetchingSitePurchases } from 'state/site'; -import { getUpgradeUrl, isAtomicSite } from 'state/initial-state'; - -/** - * Displays a card for Search based on the props given. - * - * @param {object} props Settings to render the card. - * @returns {object} Search card - */ -const renderCard = props => ( - -

{ props.content }

-
-); - -class DashSearch extends Component { - static propTypes = { - getOptionValue: PropTypes.func.isRequired, - - // Connected props - isDevMode: PropTypes.bool.isRequired, - }; - - static defaultProps = { - getOptionValue: noop, - isDevMode: false, - }; - - trackSearchLink() { - analytics.tracks.recordJetpackClick( { - type: 'upgrade-link', - target: 'at-a-glance', - feature: 'search', - } ); - } - - activateSearch = () => { - this.props.updateOptions( { - search: true, - ...( this.props.hasSearchProduct ? { instant_search_enabled: true } : {} ), - } ); - }; - - render() { - // NOTE: Jetpack Search currently does not support atomic sites. - if ( this.props.isAtomicSite ) { - return null; - } - - if ( this.props.isFetching ) { - return renderCard( { - status: '', - content: __( 'Loading…' ), - } ); - } - - if ( this.props.isDevMode ) { - return renderCard( { - className: 'jp-dash-item__is-inactive', - status: 'no-pro-uninstalled-or-inactive', - pro_inactive: true, - content: __( 'Unavailable in Dev Mode' ), - } ); - } - - if ( ! this.props.isBusinessPlan && ! this.props.hasSearchProduct ) { - return renderCard( { - className: 'jp-dash-item__is-inactive', - status: 'no-pro-uninstalled-or-inactive', - pro_inactive: true, - overrideContent: ( - - ), - } ); - } - - if ( this.props.getOptionValue( 'search' ) ) { - return ( -
- -

- { __( 'Jetpack Search is powering search on your site.' ) } -

-
- { this.props.hasSearchProduct ? ( - - { SEARCH_CUSTOMIZE_CTA } - - ) : ( - - { __( 'Add Search (Jetpack) Widget' ) } - - ) } -
- ); - } - - return renderCard( { - className: 'jp-dash-item__is-inactive', - pro_inactive: false, - content: __( - '{{a}}Activate{{/a}} to help visitors quickly find answers with highly relevant instant search results and powerful filtering.', - { - components: { - a:
, - }, - } - ), - } ); - } -} - -export default connect( state => { - return { - isAtomicSite: isAtomicSite( state ), - isBusinessPlan: 'is-business-plan' === getPlanClass( getSitePlan( state ).product_slug ), - isDevMode: isDevMode( state ), - isFetching: isFetchingSitePurchases( state ), - hasSearchProduct: hasActiveSearchPurchase( state ), - upgradeUrl: getUpgradeUrl( state, 'aag-search' ), - }; -} )( DashSearch ); diff --git a/_inc/client/at-a-glance/stats/dash-stats-bottom.jsx b/_inc/client/at-a-glance/stats/dash-stats-bottom.jsx deleted file mode 100644 index 2754b9992fd4a..0000000000000 --- a/_inc/client/at-a-glance/stats/dash-stats-bottom.jsx +++ /dev/null @@ -1,155 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import Button from 'components/button'; -import analytics from 'lib/analytics'; -import Card from 'components/card'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import { numberFormat, moment, translate as __ } from 'i18n-calypso'; - -class DashStatsBottom extends Component { - statsBottom() { - let generalStats; - if ( 'object' === typeof this.props.statsData.general ) { - generalStats = this.props.statsData.general.stats; - } else { - generalStats = { - views: '-', - comments: '-', - views_today: '-', - views_best_day: '-', - views_best_day_total: '-', - }; - } - return [ - { - viewsToday: generalStats.views_today, - bestDay: { - day: generalStats.views_best_day, - count: generalStats.views_best_day_total, - }, - allTime: { - views: generalStats.views, - comments: generalStats.comments, - }, - }, - ]; - } - - trackViewDetailedStats = () => analytics.tracks.recordJetpackClick( 'view_detailed_stats' ); - - trackViewWpcomStats = () => analytics.tracks.recordJetpackClick( 'view_wpcom_stats' ); - - render() { - const s = this.statsBottom()[ 0 ]; - - return ( -
-
-
-

- { __( 'Views today', { comment: 'Referring to a number of page views' } ) } -

-

{ s.viewsToday }

-
-
-

- { __( 'Best overall day', { comment: 'Referring to a number of page views' } ) } -

-

- { '-' === s.bestDay.count - ? '-' - : __( '%(number)s View', '%(number)s Views', { - count: s.bestDay.count, - args: { - number: numberFormat( s.bestDay.count ), - }, - } ) } -

-

- { '-' === s.bestDay.day ? '-' : moment( s.bestDay.day ).format( 'MMMM Do, YYYY' ) } -

-
-
-
-

- { __( 'All-time views', { comment: 'Referring to a number of page views' } ) } -

-

- { '-' === s.allTime.views ? '-' : numberFormat( s.allTime.views ) } -

-
-
-

- { __( 'All-time comments', { comment: 'Referring to a number of comments' } ) } -

-

- { '-' === s.allTime.comments ? '-' : numberFormat( s.allTime.comments ) } -

-
-
-
-
-
-
- { __( '{{button}}View detailed stats{{/button}}', { - components: { - button: ( -
-
- { ! this.props.isLinked && ( - - { __( 'Connect your account to WordPress.com to view more stats' ) } - - ) } -
- ); - } -} - -DashStatsBottom.propTypes = { - siteRawUrl: PropTypes.string.isRequired, - siteAdminUrl: PropTypes.string.isRequired, - statsData: PropTypes.object.isRequired, - isLinked: PropTypes.bool.isRequired, -}; - -DashStatsBottom.defaultProps = { - siteRawUrl: '', - siteAdminUrl: '', - statsData: {}, - isLinked: false, -}; - -export default DashStatsBottom; diff --git a/_inc/client/at-a-glance/stats/index.jsx b/_inc/client/at-a-glance/stats/index.jsx deleted file mode 100644 index 7b17f2171ed02..0000000000000 --- a/_inc/client/at-a-glance/stats/index.jsx +++ /dev/null @@ -1,363 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { forEach, get, isEmpty } from 'lodash'; -import Card from 'components/card'; -import Chart from 'components/chart'; -import { connect } from 'react-redux'; -import DashSectionHeader from 'components/dash-section-header'; -import Button from 'components/button'; -import Spinner from 'components/spinner'; -import { numberFormat, moment, translate as __ } from 'i18n-calypso'; -import analytics from 'lib/analytics'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import { imagePath } from 'constants/urls'; -import { isDevMode, isCurrentUserLinked, getConnectUrl } from 'state/connection'; -import { getInitialStateStatsData } from 'state/initial-state'; -import QueryStatsData from 'components/data/query-stats-data'; -import DashStatsBottom from './dash-stats-bottom'; -import { getStatsData, statsSwitchTab, fetchStatsData, getActiveStatsTab } from 'state/at-a-glance'; -import { isModuleAvailable, getModuleOverride } from 'state/modules'; -import { emptyStatsCardDismissed } from 'state/settings'; -import ModuleOverriddenBanner from 'components/module-overridden-banner'; - -export class DashStats extends Component { - static propTypes = { - isDevMode: PropTypes.bool.isRequired, - siteRawUrl: PropTypes.string.isRequired, - siteAdminUrl: PropTypes.string.isRequired, - statsData: PropTypes.any.isRequired, - isModuleAvailable: PropTypes.bool.isRequired, - }; - - constructor( props ) { - super( props ); - this.state = { - emptyStatsDismissed: props.isEmptyStatsCardDismissed, - }; - } - - barClick( bar ) { - if ( bar.data.link ) { - analytics.tracks.recordJetpackClick( 'stats_bar' ); - window.open( bar.data.link, '_blank' ); - } - } - - statsChart( unit ) { - const props = this.props, - s = []; - - let totalViews = 0; - - if ( 'object' !== typeof props.statsData[ unit ] ) { - return { chartData: s, totalViews: false }; - } - - forEach( props.statsData[ unit ].data, function( v ) { - const views = v[ 1 ]; - let date = v[ 0 ], - chartLabel = '', - tooltipLabel = ''; - - // Increment total views for the period - totalViews += views; - - if ( 'day' === unit ) { - chartLabel = moment( date ).format( 'MMM D' ); - tooltipLabel = moment( date ).format( 'MMMM Do' ); - } else if ( 'week' === unit ) { - date = date.replace( /W/g, '-' ); - chartLabel = moment( date ).format( 'MMM D' ); - tooltipLabel = __( 'Week of %(date)s', { - args: { date: moment( date ).format( 'MMMM Do' ) }, - } ); - } else if ( 'month' === unit ) { - chartLabel = moment( date ).format( 'MMM' ); - tooltipLabel = moment( date ).format( 'MMMM, YYYY' ); - } - - s.push( { - label: chartLabel, - value: views, - nestedValue: null, - className: 'statsChartbar', - data: { - link: getRedirectUrl( `calypso-stats-${ unit }`, { - site: props.siteRawUrl, - query: `startDate=${ date }`, - } ), - }, - tooltipData: [ - { - label: tooltipLabel, - value: __( 'Views: %(numberOfViews)s', { - args: { numberOfViews: numberFormat( views ) }, - } ), - className: 'tooltip class', - }, - { label: __( 'Click to view detailed stats.' ) }, - ], - } ); - } ); - - return { chartData: s, totalViews: totalViews }; - } - - /** - * Checks that the stats fetching didn't return errors. - * - * @returns {object|boolean} Returns statsData.general.errors or false if it is not an object - */ - statsErrors() { - return get( this.props.statsData, [ 'general', 'errors' ], false ); - } - - renderStatsChart( chartData ) { - return ( -
-
- - { 0 === chartData.length && } -
-
- -
-
- ); - } - - dismissCard = () => { - this.setState( { emptyStatsDismissed: true } ); - this.props.updateOptions( { dismiss_empty_stats_card: true } ); - }; - - renderEmptyStatsCard() { - return ( - - { -

- { __( 'Hello there! Your stats have been activated.' ) } -
- { __( 'Just give us a little time to collect data so we can display it for you here.' ) } -

- -
- ); - } - - activateStats = () => this.props.updateOptions( { stats: true } ); - - renderStatsArea() { - if ( this.props.getOptionValue( 'stats' ) ) { - if ( this.statsErrors() ) { - return ( -
- ); - } - - const statsChart = this.statsChart( this.props.activeTab ), - chartData = statsChart.chartData, - totalViews = statsChart.totalViews, - showEmptyStats = - chartData.length && - totalViews <= 0 && - ! this.props.isEmptyStatsCardDismissed && - ! this.state.emptyStatsDismissed; - - return ( -
- { showEmptyStats ? this.renderEmptyStatsCard() : this.renderStatsChart( chartData ) } -
- ); - } - - return ( -
- ); - } - - switchTo( timeFrame ) { - analytics.tracks.recordJetpackClick( { target: 'stats_switch_view', view: timeFrame } ); - this.props.switchView( timeFrame ); - this.props.fetchStatsData( timeFrame ); - } - - switchToDay = () => this.switchTo( 'day' ); - switchToWeek = () => this.switchTo( 'week' ); - switchToMonth = () => this.switchTo( 'month' ); - - maybeShowStatsTabs() { - const statsChart = this.statsChart( this.props.activeTab ); - - if ( - false === statsChart.totalViews && - ! this.props.isEmptyStatsCardDismissed && - ! this.state.emptyStatsDismissed - ) { - return false; - } - - if ( this.props.getOptionValue( 'stats' ) && ! this.statsErrors() ) { - return ( - - ); - } - } - - getClass( view ) { - return this.props.activeTab === view - ? 'jp-at-a-glance__stats-view-link is-current' - : 'jp-at-a-glance__stats-view-link'; - } - - render() { - if ( 'inactive' === this.props.getModuleOverride( 'stats' ) ) { - return ( -
- -
- ); - } - return ( - this.props.isModuleAvailable && ( -
- - - { this.maybeShowStatsTabs() } - - - { this.renderStatsArea() } - -
- ) - ); - } -} - -export default connect( - state => ( { - isModuleAvailable: isModuleAvailable( state, 'stats' ), - activeTab: getActiveStatsTab( state ), - isDevMode: isDevMode( state ), - isLinked: isCurrentUserLinked( state ), - connectUrl: getConnectUrl( state ), - statsData: isEmpty( getStatsData( state ) ) - ? getInitialStateStatsData( state ) - : getStatsData( state ), - isEmptyStatsCardDismissed: emptyStatsCardDismissed( state ), - getModuleOverride: module_name => getModuleOverride( state, module_name ), - } ), - dispatch => ( { - switchView: tab => dispatch( statsSwitchTab( tab ) ), - fetchStatsData: range => dispatch( fetchStatsData( range ) ), - } ) -)( DashStats ); diff --git a/_inc/client/at-a-glance/stats/test/component.js b/_inc/client/at-a-glance/stats/test/component.js deleted file mode 100644 index 97adcb4bef969..0000000000000 --- a/_inc/client/at-a-glance/stats/test/component.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { expect } from 'chai'; -import { shallow } from 'enzyme'; -import getRedirectUrl from 'lib/jp-redirect'; - -/** - * Internal dependencies - */ -import { DashStats } from '../index'; - -describe( 'Dashboard Stats', () => { - let wrapper, - testProps; - - before( function() { - testProps = { - siteRawUrl: 'example.org', - siteAdminUrl: 'example.org/wp-admin', - statsData: { - general: { - date: '2018-03-21', - stats: { - visitors_today: 0, - visitors_yesterday: 0, - visitors: 24, - views_today: 0, - views_yesterday: 0, - views_best_day: '2017-05-18', - views_best_day_total: 11, - views: 59, - comments: 6, - posts: 11, - followers_blog: 0, - followers_comments: 0, - comments_per_month: 0, - comments_most_active_recent_day: '2016-03-08 20:37:56', - comments_most_active_time: '17:00', - comments_spam: 0, - categories: 3, - tags: 0, - shares: 0, - shares_twitter: 0, - 'shares_google-plus-1': 0, - 'shares_custom-1513105119': 0, - shares_facebook: 0 - }, - visits: { - unit: 'day', - fields: [ 'period', 'views', 'visitors' ], - data: [ [ '2018-02-20', 0, 0 ] ] - } - }, - day: undefined, - }, - isModuleAvailable: true, - isDevMode: false, - moduleList: { stats: {} }, - activeTab: 'day', - isLinked: true, - connectUrl: getRedirectUrl( 'calypso-jetpack-connect' ), - isEmptyStatsCardDismissed: false, - getOptionValue: module => 'stats' === module, - getModuleOverride: () => false, - }; - } ); - - describe( 'Initially', () => { - before( function() { - wrapper = shallow( ); - } ); - - it( 'renders header and card', () => { - expect( wrapper.find( 'DashSectionHeader' ) ).to.have.length( 1 ); - expect( wrapper.find( '.jp-at-a-glance__stats-card' ) ).to.have.length( 1 ); - } ); - - it( 'does not render date range tabs', () => { - expect( wrapper.find( '.jp-at-a-glance__stats-views' ) ).to.have.length( 0 ); - } ); - - describe( 'when stats are present, but empty', function() { - before( function() { - testProps.statsData.day = { - unit: 'day', - fields: [ 'period', 'views', 'visitors' ], - // Mock no views for this date - data: [ [ '2018-02-20', 0, 0 ] ] - }; - wrapper = shallow( ); - } ); - - it( 'renders the empty stats container', () => { - expect( wrapper.find( '.jp-at-a-glance__stats-empty' ) ).to.have.length( 1 ); - } ); - } ); - } ); - - describe( 'When empty stats card was dismissed', () => { - before( function() { - wrapper = shallow( ); - } ); - - it( 'renders date range tabs', () => { - expect( wrapper.find( '.jp-at-a-glance__stats-views' ) ).to.have.length( 1 ); - } ); - } ); - - describe( 'When there is stats data', () => { - before( function() { - testProps.statsData.day = { - unit: 'day', - fields: [ 'period', 'views', 'visitors' ], - // Mock 32 views for this date - data: [ [ '2018-02-20', 32, 0 ] ] - }; - wrapper = shallow( - - ); - } ); - it( 'renders some stats', () => { - expect( wrapper.find( '.jp-at-a-glance__stats-chart' ) ).to.have.length( 1 ); - } ); - it( 'and range tabs', () => { - expect( wrapper.find( '.jp-at-a-glance__stats-views' ) ).to.have.length( 1 ); - } ); - } ); -} ); diff --git a/_inc/client/at-a-glance/style.scss b/_inc/client/at-a-glance/style.scss deleted file mode 100644 index 431a2a02d8e78..0000000000000 --- a/_inc/client/at-a-glance/style.scss +++ /dev/null @@ -1,440 +0,0 @@ -// This document contains styles that influence the styles of Stats & At a Glance, overall. -// For more precise styles, dig into the styles of each component. ie. dash-item -@import '../scss/calypso-colors'; - -.jp-at-a-glance { - margin-bottom: rem( 48px ); -} - -.jp-at-a-glance__stats-card { - padding: 0; -} - -.jp-at-a-glance__stats-empty { - text-align: center; - margin-bottom: 0; - - p { - font-size: rem( 14px ); - color: $gray-text-min; - } -} - -.jp-at-a-glance__stats-inactive { - padding: rem( 16px ); - - @include breakpoint( '>660px' ) { - display: flex; - flex-wrap: nowrap; - flex-direction: row; - align-items: center; - } -} - -.jp-at-a-glance__stats-inactive-icon { - @include breakpoint( '<660px' ) { - display: none; - } - - @include breakpoint( '>660px' ) { - flex-basis: 10%; - } -} - -.jp-at-a-glance__stats-inactive-text { - font-size: rem( 14px ); - line-height: 1.5; - - @include breakpoint( '<660px' ) { - padding: 0 0 rem( 16px ); - } - - @include breakpoint( '>660px' ) { - flex-basis: 50%; - padding: 0 rem( 16px ); - } -} - -.jp-at-a-glance__stats-inactive-button { - text-align: left; - - @include breakpoint( '>660px' ) { - flex-basis: 40%; - text-align: right; - } -} - -.jp-at-a-glance__stats-chart { - padding: rem( 16px ); - position: relative; -} - -.jp-at-a-glance__stats-chart .dops-spinner { - position: absolute; - top: 50%; - left: 50%; -} - -.jp-at-a-glance__stats-bottom { - margin: rem( 32px ) 0 0; - - @include breakpoint( '<480px' ) { - box-shadow: 0 0 0 1px $light-gray-700; - } -} - -.jp-at-a-glance__stats-summary { - text-align: center; - border-bottom: 1px $light-gray-700 solid; - - @include breakpoint( '>660px' ) { - flex-wrap: nowrap; - display: flex; - flex-direction: row; - } - @include breakpoint( '<660px' ) { - display: block; - } -} - -.jp-at-a-glance__stats-summary-today, -.jp-at-a-glance__stats-summary-bestday { - flex-basis: 25%; - padding: rem( 16px ); - box-shadow: 0 0 0 1px $light-gray-700; -} - -.jp-at-a-glance__stats-summary-today { - @include breakpoint( '<660px' ) { - margin-top: rem( -1px ); - } -} - -.jp-at-a-glance__stats-summary-bestday { - @include breakpoint( '>660px' ) { - margin: 0 rem( 1px ); - } -} - -.jp-at-a-glance__stats-summary-bestday, -.jp-at-a-glance__stats-summary-alltime { - @include breakpoint( '<660px' ) { - margin-top: rem( 1px ); - } -} - -.jp-at-a-glance__stats-summary-alltime { - flex-basis: 50%; - padding: rem( 16px ); - box-shadow: 0 0 0 1px $light-gray-700; - - @include breakpoint( '>660px' ) { - max-width: 50%; // for IE 10 since it doesn't recognize flex-basis - display: flex; - flex-grow: 1; - flex-shrink: 1; - } -} - -.jp-at-a-glance__stats-alltime-views, -.jp-at-a-glance__stats-alltime-comments { - flex-basis: 50%; -} - -.jp-at-a-glance__stats-cta { - padding: rem( 16px ); - background-color: lighten( $gray, 35% ); - - @include breakpoint( '>660px' ) { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - } - @include breakpoint( '<660px' ) { - display: block; - } -} - -.jp-at-a-glance__stats-cta-description { - @include breakpoint( '>660px' ) { - flex-basis: 30%; - } -} - -.jp-at-a-glance__stat-details { - margin: 0; -} - -.jp-at-a-glance__stat-number { - font-size: rem( 22px ); - font-weight: 400; - margin: rem( 8px ) 0; -} - -.jp-at-a-glance__stats-cta-buttons { - @include breakpoint( '>660px' ) { - text-align: right; - flex-basis: 70%; - } - - @include breakpoint( '<660px' ) { - text-align: center; - - .dops-button { - width: 100%; - margin-bottom: rem( 4px ); - } - } - - .dops-button { - text-align: center; - margin: rem( 4px ); - } -} - -.jp-at-a-glance__stats-views { - margin-top: 0; - margin-bottom: 0; -} - -.jp-at-a-glance__stats-view { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - margin-left: rem( 16px ); - - &:focus { - outline: 0; - } - - @include breakpoint( '<480px' ) { - margin-left: 0; - margin-right: rem( 16px ); - } -} - -.jp-at-a-glance__stats-view-link, -.jp-at-a-glance__stats-view-link:visited { - color: $gray; - text-decoration: underline; - - &.is-current, - &:visited.is-current, - &:focus.is-current { - color: #23282d; - text-decoration: none; - } -} - -.jp-at-a-glance__stats-view-link:focus { - outline: 0; - box-shadow: none; -} - -// heavy flexbox nesting below, careful! – @jeffgolenski -.jp-at-a-glance__item-grid { - display: flex; - - @include breakpoint( '<660px' ) { - display: block; // don't need flexbox on smaller screens - } -} - -.jp-at-a-glance__left, -.jp-at-a-glance__right { - display: flex; - min-width: 0; - - @include breakpoint( '>660px' ) { - flex-basis: 50%; - margin-bottom: rem( 16px ); - } - @include breakpoint( '<660px' ) { - margin-bottom: rem( 12px ); - } - - > div, // Sorry. not sure how to class this. Thanks, React. - .jp-dash-item { - min-width: 0; // weird fix for overflowing items - flex-grow: 1; - display: flex; - flex-direction: column; - } - > div { - flex-basis: 100%; - } - .jp-dash-item { - .dops-card { - flex-grow: 1; - } - .dops-card.is-compact { - flex-grow: 0; - a.dops-notice__action { - margin-left: 0; - padding-left: 0; - @include breakpoint( '<480px' ) { - text-transform: none; - } - } - } - } - .jp-dash-item__card { - display: flex; - } -} - -.jp-search-config-aag { - width: 100%; -} - -.jp-at-a-glance__left { - display: flex; - - @include breakpoint( '>660px' ) { - margin-right: rem( 16px ); - - &:last-child { - flex-basis: calc( 50% - 0.5rem ); // rem function doesn't work in calc() - } - } -} -// end flexbox nesting - -.dops-chart__tooltip .tip-arrow { - display: none; -} - -// Connection Settings -.jp-connection-type { - .jp-dash-item__card { - align-items: flex-start; - } -} -.jp-connection-settings__info { - display: flex; -} - -.jp-connection-settings__actions { - margin: 1em 0 0; - - a { - cursor: pointer; - } -} - -.jp-connection-settings__text { - width: 70%; - margin-left: rem( 16px ); - word-break: break-word; -} -.jp-connection-settings__info { - .gridicon { - opacity: 0.6; - } - .gridicon, - .jp-connection-settings__site-icon { - background: #c8d7e1; - color: #fff; - min-width: rem( 64px ); - } - .jp-connection-settings__gravatar { - display: inline-block; - min-width: rem( 64px ); - background: $gray; - border-radius: 50%; - margin-bottom: 0; - } -} - -.jp-connection-settings__username { - font-weight: 600; -} - -.jp-connection-settings__email { - color: $gray; - font-size: rem( 13px ); - font-style: italic; - font-weight: 400; -} - -.jp-connection-settings__modal.dops-modal { - max-width: 635px; - min-height: 400px; - - @include breakpoint( '>660px' ) { - min-height: auto; - } -} - -.jp-connection-settings__modal-body { - margin: 0; - padding: rem( 24px ) rem( 32px ); - font-size: rem( 14px ); - color: $blue-dark-text; - text-align: center; - - h2 { - margin: rem( 32px ) 0 rem( 24px ); - font-size: rem( 32px ); - font-weight: 300; - color: $blue-dark-text; - } - - h4 { - margin: rem( 16px ) rem( 24px ) 0; - font-size: rem( 16px ); - font-weight: 400; - line-height: 1.5em; - color: $blue-heading; - } - - p { - font-size: rem( 14px ); - } - - ul { - margin: rem( 24px ) 0 rem( 36px ); - color: $blue-text; - } - - li { - position: relative; - display: block; - margin: 0; - padding: rem( 16px ) rem( 8px ) rem( 16px ) rem( 44px ); - border-bottom: 1px solid lighten( $gray, 25% ); - text-align: left; - - &:first-of-type { - border-top: 1px solid lighten( $gray, 25% ); - } - } - - .gridicon { - position: absolute; - left: rem( 16px ); - top: rem( 16px ); - vertical-align: text-bottom; - color: $blue-text; - } -} - -.jp-connection-settings__modal-cancel { - margin-right: 1em; -} - -.jp-connection-settings__modal-more a { - color: $blue-wordpress; - text-decoration: underline; -} - -.jp-dash-item__manage-in-wpcom { - margin-top: 1px; - width: 100%; -} - -.jp-dash-item .dops-banner.dops-banner { - width: 100%; - margin-bottom: 0; -} diff --git a/_inc/client/at-a-glance/test/component.js b/_inc/client/at-a-glance/test/component.js deleted file mode 100644 index 3a16d0274daf2..0000000000000 --- a/_inc/client/at-a-glance/test/component.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { expect } from 'chai'; -import { shallow } from 'enzyme'; - -/** - * Internal dependencies - */ -import { DashConnections } from '../connections'; - -describe( 'Connections', () => { - let testProps = { - siteConnectionStatus: true, - isDevMode: false, - userCanDisconnectSite: true, - userIsMaster: true, - isLinked: true, - userWpComLogin: 'jetpack', - userWpComEmail: 'jetpack', - userWpComAvatar: 'https://example.org/avatar.png', - username: 'jetpack', - siteIcon: 'https://example.org/site-icon.png' - }; - - describe( 'Initially', () => { - - const wrapper = shallow( ); - - it( 'renders correctly', () => { - expect( wrapper.find( '.jp-connection-type' ) ).to.have.length( 2 ); - } ); - - it( 'renders cards for site and user connection', () => { - expect( wrapper.find( '.jp-connection-settings__info' ) ).to.have.length( 2 ); - } ); - - } ); - - describe( 'Site connection', () => { - - const wrapper = shallow( ); - - it( 'indicates if user is the connection owner', () => { - expect( wrapper.find( '.jp-connection-settings__is-owner' ) ).to.have.length( 1 ); - } ); - - it( 'displays the site icon if it exists', () => { - expect( wrapper.find( '.jp-connection-settings__site-icon' ) ).to.have.length( 1 ); - } ); - - it( 'shows a disconnection link', () => { - expect( wrapper.find( 'Connect(ConnectButton)' ) ).to.have.length( 1 ); - } ); - - it( 'if there is no site icon a Gridicon is displayed', () => { - expect( shallow( ).find( 'Gridicon' ) ).to.have.length( 1 ); - } ); - - } ); - - describe( 'when site is in Dev Mode', () => { - - const wrapper = shallow( ); - - it( 'does not show a disconnection link', () => { - expect( wrapper.find( 'Connect(ConnectButton)' ) ).to.have.length( 0 ); - } ); - - } ); - - describe( 'User connection', () => { - - const wrapper = shallow( ).find( '.jp-connection-type' ).at( 1 ); - - it( 'shows an avatar if user is linked', () => { - expect( wrapper.find( 'img' ) ).to.have.length( 1 ); - } ); - - it( 'does not show a disconnection link for master users', () => { - expect( wrapper.find( 'Connect(ConnectButton)' ) ).to.have.length( 0 ); - } ); - - } ); - - describe( 'when user is not linked', () => { - - const wrapper = shallow( ).find( '.jp-connection-type' ).at( 1 ); - - it( 'shows a link to connect the account', () => { - expect( wrapper.find( 'Connect(ConnectButton)' ) ).to.have.length( 1 ); - - } ); - - it( 'does not show an avatar', () => { - expect( wrapper.find( 'img' ) ).to.have.length( 0 ); - } ); - - } ); - -} ); diff --git a/_inc/client/components/about-page/style.scss b/_inc/client/components/about-page/style.scss deleted file mode 100644 index 174c90e398440..0000000000000 --- a/_inc/client/components/about-page/style.scss +++ /dev/null @@ -1,184 +0,0 @@ -$color__border: #e5e5e5; -$color__back-link: #6c7781; - -.jetpack-about__link-back, -.jetpack-about__main, -.jetpack-about__plugin { - background-color: $white; - border: 1px solid $color__border; -} - -.jetpack-about__main, -.jetpack-about__colophon { - font-size: 14px; - - p { - font-size: 14px; - } -} - -.jetpack-about__link-back { - padding: 16px 16px 13px; - border-bottom: none; - - a { - color: $color__back-link; - font-size: 0.75rem; - - &:hover, - &:active { - color: darken( $color__back-link, 30% ); - } - - svg { - fill: $color__back-link; - height: 24px; - margin-right: 4px; - margin-top: -3px; - vertical-align: middle; - width: 24px; - } - } -} - -.jetpack-about__main { - padding: 36px; -} - -.jetpack-about__logo { - width: 265px; - - @media screen and (max-width: 400px) { - width: 100%; - } -} -.jetpack-about__content { - display: flex; - - @media screen and (max-width: 782px) { - flex-wrap: wrap; - } -} - -.jetpack-about__text { - order: 1; - - p { - font-size: 1rem; - line-height: 1.6; - - &:last-child { - margin-bottom: 0; - } - } -} - -.jetpack-about__images { - text-align: center; - margin-left: 48px; - order: 2; - - @media screen and (max-width: 782px) { - margin-left: 0; - order: 0; - text-align: left; - width: 100%; - } - - .meet-the-team { - margin-bottom: 0; - text-align: center; - } -} - -.jetpack-about__gravatars { - display: flex; - flex-wrap: wrap; - width: 320px; - - @media screen and (max-width: 450px) { - max-width: 320px; - width: 100%; - } - - li { - margin-bottom: 0; - width: 25%; - - img { - max-width: 80px; - vertical-align: top; - width: 100%; - height: 100%; - } - } -} - -.jetpack-about__colophon { - margin-bottom: 3rem; - - h3 { - margin-top: 2.4375rem; - } -} - -.jetpack-about__services-more { - margin-top: 1.5em; - text-align: center; -} - - -.jetpack-about__services { - display: grid; - grid-template-columns: 1fr 1fr; - grid-gap: 16px; -} - -.jetpack-about__plugin { - margin-bottom: 0; - - .plugin-card-top { - padding: 20px 20px 15px; - position: relative; - - .name, - .desc, - .details-link { - margin-left: 148px; - } - - .name h3 { - margin-top: 0; - } - - .details-link { - font-weight: 500; - } - } - - .plugin-card-bottom { - align-items: center; - background-color: $white; - display: flex; - flex-wrap: wrap; - justify-content: space-between; - - .meta { - margin-right: 1em; - } - - .num-ratings { - display: inline-block; - } - - .action-buttons { - clear: none; - float: none; - margin: 0.5em 0; - - li:last-child { - margin-bottom: 0; - } - } - } -} diff --git a/_inc/client/components/admin-notices/index.jsx b/_inc/client/components/admin-notices/index.jsx deleted file mode 100644 index 284cfccda8e4e..0000000000000 --- a/_inc/client/components/admin-notices/index.jsx +++ /dev/null @@ -1,139 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; - -class AdminNotices extends React.Component { - componentDidMount() { - const $adminNotices = jQuery( this.refs.adminNotices ); - const dismiss = - ''; - - const $vpDeactivationNotice = jQuery( '.vp-deactivated' ); - if ( $vpDeactivationNotice.length > 0 ) { - $vpDeactivationNotice.each( function() { - const $notice = jQuery( this ) - .addClass( 'dops-notice is-success is-dismissable' ) - .removeClass( 'wrap vp-notice notice notice-success' ); - const icon = - ''; - $notice.wrapInner( '' ); - $notice - .find( '.dops-notice__content' ) - .before( icon ) - .css( 'display', 'block' ); - $notice.find( '.dops-notice__content' ).after( dismiss ); - $notice.find( 'h2' ).replaceWith( function() { - return jQuery( '', { html: this.innerHTML } ); - } ); - $notice.find( 'p' ).replaceWith( function() { - return jQuery( '
', { html: this.innerHTML } ); - } ); - $notice.prependTo( $adminNotices ).css( 'display', 'flex' ); - } ); - } - - const $vpNotice = jQuery( '.vp-notice' ); - if ( $vpNotice.length > 0 ) { - $vpNotice.each( function() { - const $notice = jQuery( this ); - // If the notice doesn't have an icon, it's one of the old VP notices. - if ( 0 === $notice.find( '.dops-notice__icon' ).length ) { - const $success = $notice.hasClass( 'vp-registered' ); - const $warningOrSuccess = $success ? 'is-success' : 'is-error'; - $notice.addClass( 'dops-notice vp-notice-jp ' + $warningOrSuccess ); - $notice.wrapInner( '' ); - const icon = $success - ? '' - : ''; - $notice.find( '.dops-notice__content' ).before( icon ); - $notice - .find( '.vp-message' ) - .removeClass( 'vp-message' ) - .addClass( 'dops-notice__text' ); - $notice.find( 'h3' ).replaceWith( function() { - return jQuery( '', { html: this.innerHTML } ); - } ); - $notice.find( 'p' ).replaceWith( function() { - return jQuery( '
', { html: this.innerHTML } ); - } ); - $notice.css( 'display', 'flex' ); - } - $notice.find( 'a[href*="admin.php?page=vaultpress"]' ).remove(); - $notice.prependTo( $adminNotices ).removeClass( 'wrap vp-notice' ); - } ); - } - - const $wcNotice = jQuery( '.woocommerce-message' ); - if ( $wcNotice.length > 0 ) { - $wcNotice.each( function() { - const $notice = jQuery( this ) - .addClass( 'dops-notice' ) - .removeClass( 'updated wc-connect' ); - $notice - .find( '.button-primary' ) - .addClass( 'dops-notice__action' ) - .removeClass( 'button-primary' ) - .detach() - .appendTo( $notice ); - $notice - .find( 'p' ) - .not( '.submit' ) - .wrapAll( '' ); - const $dopsNotice = $notice.find( '.dops-notice__text' ); - $dopsNotice.find( 'p' ).replaceWith( function() { - return jQuery( '
', { html: this.innerHTML, class: 'dops-notice__moved_text' } ); - } ); - $dopsNotice.find( 'br' ).remove(); - $notice - .find( '.button-secondary' ) - .removeClass( 'button-secondary' ) - .detach() - .appendTo( $dopsNotice ); - $notice.find( '.submit' ).remove(); - $notice - .find( '.woocommerce-message-close' ) - .removeClass( 'woocommerce-message-close notice-dismiss' ) - .addClass( 'dops-notice__action' ); - $notice - .wrapInner( '' ) - .prependTo( $adminNotices ) - .css( 'display', 'flex' ); - $notice - .find( '.dops-notice__action' ) - .not( ':first' ) - .removeClass( 'dops-notice__action' ) - .detach() - .appendTo( $notice.find( '.dops-notice__text' ) ); - $notice - .find( '.dops-notice__action:first' ) - .detach() - .appendTo( $notice ); - } ); - } - - // Hide the rest of the core notices, they don't look very good above the react app. - const $allNotices = jQuery( '.notice' ); - if ( $allNotices.length > 0 ) { - $allNotices.each( function() { - const $notice = jQuery( this ); - $notice.hide(); - } ); - } - - if ( $adminNotices.length > 0 ) { - jQuery( '.dops-notice__dismiss' ).click( function() { - jQuery( this ) - .parent() - .closest( 'div' ) - .hide(); - } ); - } - } - - render() { - return
; - } -} - -export default AdminNotices; diff --git a/_inc/client/components/admin-notices/style.scss b/_inc/client/components/admin-notices/style.scss deleted file mode 100644 index 57ecd84ca4bb4..0000000000000 --- a/_inc/client/components/admin-notices/style.scss +++ /dev/null @@ -1,45 +0,0 @@ -.jetpack-pagestyles { - // Initially hide VaultPress and WooCommerce notices until they're displayed in Jetpack notices area - .vp-notice, .woocommerce-message, .wc-connect { - display: none; - } - - .vp-notice-jp { - a { - text-decoration: underline; - } - } - - .woocommerce-message.dops-notice { - display: block; - padding: 0; - - &::before { - content: ''; - } - - .submit { - padding: 0; - } - - .notice-dismiss::before { - display: none; - } - - .dops-notice__text > div { - max-width: 620px; - } - - .dops-notice__text > a { - margin-right: 15px; - } - - .dops-notice__moved_text { - margin-bottom: 5px; - } - } - - .dops-notice__action.notice-dismiss { - height: 100%; - } -} \ No newline at end of file diff --git a/_inc/client/components/apps-card/README.md b/_inc/client/components/apps-card/README.md deleted file mode 100644 index a8757bc810449..0000000000000 --- a/_inc/client/components/apps-card/README.md +++ /dev/null @@ -1,19 +0,0 @@ -Apps Card -============== - -This component is used to display a WordPress apps card, quickly highlighting the vast device support, a few benefits, followed by a CTA. - -#### How to use: - -```js - -var AppsCard = require( 'components/apps-card' ) - -render: function() { - return ( - - ); -} - -} -``` \ No newline at end of file diff --git a/_inc/client/components/apps-card/index.jsx b/_inc/client/components/apps-card/index.jsx deleted file mode 100644 index d42ba803c0131..0000000000000 --- a/_inc/client/components/apps-card/index.jsx +++ /dev/null @@ -1,109 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React from 'react'; -import { connect } from 'react-redux'; -import classNames from 'classnames'; -import { translate as __ } from 'i18n-calypso'; -import Card from 'components/card'; -import Button from 'components/button'; -import analytics from 'lib/analytics'; - -/** - * Internal dependencies - */ -import { imagePath } from 'constants/urls'; -import { updateSettings, appsCardDismissed } from 'state/settings'; -import { arePromotionsActive, userCanManageOptions } from 'state/initial-state'; - -class AppsCard extends React.Component { - static displayName = 'AppsCard'; - - trackDownloadClick = () => { - analytics.tracks.recordJetpackClick( { - target: 'apps-card', - button: 'apps-download', - page: this.props.path, - } ); - }; - - dismissCard = () => { - this.props.dismissAppCard(); - analytics.tracks.recordJetpackClick( { - target: 'apps-card', - button: 'dismiss', - page: this.props.path, - } ); - }; - - render() { - if ( ! this.props.arePromotionsActive || this.props.isAppsCardDismissed ) { - return null; - } - - const classes = classNames( this.props.className, 'jp-apps-card' ); - - return ( -
- - { this.props.userCanManageOptions && ( - - ) } -
- -
- -
-

- { __( 'Get WordPress Apps for every device' ) } -

- -

- { __( - 'Manage all your sites from a single dashboard: publish content, track stats, moderate comments, and so much more from anywhere in the world.' - ) } -

- - -
-
-
- ); - } -} - -AppsCard.propTypes = { - className: PropTypes.string, -}; - -export default connect( - state => { - return { - isAppsCardDismissed: appsCardDismissed( state ), - arePromotionsActive: arePromotionsActive( state ), - userCanManageOptions: userCanManageOptions( state ), - }; - }, - dispatch => { - return { - dismissAppCard: () => { - return dispatch( updateSettings( { dismiss_dash_app_card: true } ) ); - }, - }; - } -)( AppsCard ); diff --git a/_inc/client/components/apps-card/style.scss b/_inc/client/components/apps-card/style.scss deleted file mode 100644 index d72ee62f536bd..0000000000000 --- a/_inc/client/components/apps-card/style.scss +++ /dev/null @@ -1,69 +0,0 @@ -.jp-apps-card { - margin-top: rem( 64px ); - margin-bottom: 0; -} - -.jp-apps-card__content { - margin-bottom: 0; - - &.dops-card { - padding: 0; - } - - a:not( .dops-button ) { - font-style: italic; - } -} - -.jp-apps-card__dismiss { - position: absolute; - top: 8px; - right: 8px; - - &.dops-button.is-compact { - padding: 8px 8px 2px 8px; - } -} - -.jp-apps-card__top { - padding: rem( 60px ) 0 0; - background: #ffffff; - text-align: center; - - img { - max-width: 26%; - padding-top: 10px; - } -} - -.jp-apps-card__description { - max-width: 80%; - margin: 0 auto; - padding: rem( 10px ) rem( 24px ) rem( 24px ); - line-height: 1.65; - color: $black; - text-align: center; - - .dops-button { - margin: 8px 0; - } -} - -.jp-apps-card__paragraph { - font-size: rem( 15px ); -} - -.jp-apps-card__header { - margin-top: 0; - margin-bottom: rem( 5px ); - font-weight: 500; -} - -.jp-apps-card__promo_subhead { - margin-top: 0; - font-style: italic; -} - -.jp-themes-card { - margin-bottom: rem( 20px ); -} diff --git a/_inc/client/components/banner/README.md b/_inc/client/components/banner/README.md deleted file mode 100644 index f3274a7c26dc4..0000000000000 --- a/_inc/client/components/banner/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Banner - -This component renders a customizable banner. - -## Props: - -- *callToAction* - shows a CTA text. -- *className* - any additional CSS classes. -- *description* - the banner description. -- *dismissPreferenceName*: the user preference name that we store a boolean against, prefixed with 'dismissible-card-' to avoid namespace collisions. -- *dismissTemporary*: when true, clicking on the cross will dismiss the card for the current page load. -- *event* - event to distinguish the nudge in tracks. Used as `cta_name` event property. -- *feature* - slug of the feature to highlight in the plans compare card. -- *href* - the component target URL. -- *icon* - the component icon. -- *list* - a list of the upgrade features. -- *onClick* - a function associated to the click on the whole banner or just the CTA or dismiss button. -- *plan* - PlanSlug of the plan that upgrade leads to. -- *price* - one or two (original/discounted) upgrade prices. -- *title* - (required) the banner title. - -If `href` is not provided, `feature` can auto-generate it. - -If `callToAction` is provided, `href` and `onClick` are not applied to the whole banner, but to the `callToAction` button only. - -If `dismissPreferenceName` is provided, `href` is only applied if `callToAction` is provided. - -## Usage: - -```js -import { PLAN_BUSINESS, FEATURE_ADVANCED_SEO } from 'lib/plans/constants'; -import Banner from 'components/banner'; - -render() { - return ( - - ); -} -``` diff --git a/_inc/client/components/banner/index.jsx b/_inc/client/components/banner/index.jsx deleted file mode 100644 index fe8198bd92017..0000000000000 --- a/_inc/client/components/banner/index.jsx +++ /dev/null @@ -1,160 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import classNames from 'classnames'; -import { noop, size } from 'lodash'; - -/** - * Internal dependencies - */ -import analytics from 'lib/analytics'; -import { getPlanClass } from 'lib/plans/constants'; -import Button from 'components/button'; -import Card from 'components/card'; -import Gridicon from 'components/gridicon'; -import PlanIcon from 'components/plans/plan-icon'; -import { getCurrentVersion } from 'state/initial-state'; - -import './style.scss'; - -class Banner extends Component { - static propTypes = { - callToAction: PropTypes.string, - className: PropTypes.string, - currentVersion: PropTypes.string.isRequired, - description: PropTypes.node, - eventFeature: PropTypes.string, - feature: PropTypes.string, // PropTypes.oneOf( getValidFeatureKeys() ), - href: PropTypes.string, - icon: PropTypes.string, - list: PropTypes.arrayOf( PropTypes.string ), - onClick: PropTypes.func, - path: PropTypes.string, - plan: PropTypes.string, - siteSlug: PropTypes.string, - title: PropTypes.string.isRequired, - }; - - static defaultProps = { - onClick: noop, - }; - - getHref() { - const { href, feature, siteSlug } = this.props; - - if ( ! href && siteSlug ) { - if ( feature ) { - return `/plans/${ siteSlug }?feature=${ feature }`; - } - return `/plans/${ siteSlug }`; - } - return href; - } - - handleClick = () => { - this.props.onClick(); - - const { eventFeature, path, currentVersion } = this.props; - if ( eventFeature || path ) { - const eventFeatureProp = eventFeature ? { feature: eventFeature } : {}; - const pathProp = path ? { path } : {}; - - const eventProps = { - target: 'banner', - type: 'upgrade', - current_version: currentVersion, - ...eventFeatureProp, - ...pathProp, - }; - - analytics.tracks.recordJetpackClick( eventProps ); - } - }; - - getIcon() { - const { icon, plan } = this.props; - - if ( plan && ! icon ) { - return ( -
- -
- ); - } - - return ( -
-
- -
-
- -
-
- ); - } - - getContent() { - const { callToAction, description, list, title } = this.props; - - return ( -
-
-
{ title }
- { description &&
{ description }
} - { size( list ) > 0 && ( -
    - { list.map( ( item, key ) => ( -
  • - - { item } -
  • - ) ) } -
- ) } -
- { callToAction && ( -
- { callToAction && ( - - ) } -
- ) } -
- ); - } - - render() { - const { callToAction, className, plan } = this.props; - const planClass = getPlanClass( plan ); - - const classes = classNames( - 'dops-banner', - className, - { 'has-call-to-action': callToAction }, - { 'is-upgrade-personal': 'is-personal-plan' === planClass }, - { 'is-upgrade-premium': 'is-premium-plan' === planClass }, - { 'is-upgrade-business': 'is-business-plan' === planClass } - ); - - return ( - - { this.getIcon() } - { this.getContent() } - - ); - } -} - -export default connect( state => ( { - currentVersion: getCurrentVersion( state ), -} ) )( Banner ); diff --git a/_inc/client/components/banner/style.scss b/_inc/client/components/banner/style.scss deleted file mode 100644 index 535b242c1f003..0000000000000 --- a/_inc/client/components/banner/style.scss +++ /dev/null @@ -1,221 +0,0 @@ -@import '../../scss/layout'; -@import "../../scss/variables/colors"; -@import "../../scss/color-functions"; - -@mixin banner-color( $color ) { - border-left-color: $color; - .dops-banner__icon { - color: $color; - } - .dops-banner__icon-circle { - background-color: $color; - } -} - -.dops-banner.dops-card { - border-left: 3px solid; - display: flex; - padding: 12px 6px 12px 12px; - position: relative; - z-index: 2; - - &.is-card-link { - padding: 12px 48px 12px 16px; - } - &.is-dismissible { - padding-right: 48px; - } - - @include banner-color( $blue-wordpress ); - - &.is-jetpack-info { - @include banner-color( $green-primary ); - } - - &.is-upgrade-personal { - @include banner-color( $alert-yellow ); - } - &.is-upgrade-premium { - @include banner-color( $alert-green ); - } - &.is-upgrade-business { - @include banner-color( $alert-purple ); - } - - .dops-card__link-indicator { - align-items: center; - color: $blue-wordpress; - display: flex; - } - - &:hover { - transition: all 100ms ease-in-out; - &.is-card-link { - box-shadow: 0 0 0 1px $gray, 0 2px 4px lighten( $gray, 20% ); - } - .dops-card__link-indicator { - color: $blue-dark; - } - } - - @include breakpoint( ">480px" ) { - padding: 12px 16px; - &.is-dismissible { - padding-right: 16px; - } - } -} - -.dops-banner__icons { - display: flex; - - .dops-banner__icon, - .dops-banner__icon-circle { - border-radius: 50%; - flex-shrink: 0; - height: 24px; - margin-right: 16px; - margin-top: -2px; - text-align: center; - top: 4px; - width: 24px; - } - - .dops-banner__icon { - align-self: center; - color: $white; - display: block; - } - - .dops-banner__icon-circle { - color: white; - display: none; - padding: 3px 4px 4px 3px; - - .gridicon { - margin-bottom: -7px; - } - } - - @include breakpoint( ">480px" ) { - align-items: center; - - .dops-banner__icon { - display: none; - } - .dops-banner__icon-circle { - display: block; - } - } -} - -.dops-banner__icon-plan { - display: flex; - margin-right: 16px; - - .dops-plan-icon { - height: 32px; - width: 32px; - } - - @include breakpoint( ">480px" ) { - align-items: center; - } -} - -.dops-banner__content { - align-items: center; - display: flex; - flex-grow: 1; - flex-wrap: wrap; - - @include breakpoint( ">480px" ) { - flex-wrap: nowrap; - } -} - -.dops-banner__info { - flex-grow: 1; - line-height: 1.4; - width: 100%; - - .dops-banner__title, - .dops-banner__description, - .dops-banner__list { - color: $gray-dark; - } - - .dops-banner__title { - font-size: 14px; - font-weight: 500; - } - - .dops-banner__description { - font-size: 12px; - margin-top: 3px; - } - - .dops-banner__list { - font-size: 12px; - list-style: none; - margin: 0; - li { - margin: 6px 0; - .gridicon { - color: $gray; - display: none; - } - } - } - - @include breakpoint( ">480px" ) { - width: auto; - - .dops-banner__list li .gridicon { - display: inline; - margin-right: 12px; - vertical-align: bottom; - } - } -} - -.dops-banner__action { - align-self: center; - font-size: 12px; - margin: 8px 0 0 0; - text-align: left; - width: 100%; - - .dops-banner__prices { - display: flex; - justify-content: flex-start; - - .dops-plan-price { - margin-bottom: 0; - } - - .dops-plan-price.is-discounted, - .dops-plan-price.is-discounted .dops-plan-price__currency-symbol { - color: $gray-dark; - } - - .has-call-to-action & .dops-plan-price { - margin-bottom: 8px; - } - } - - @include breakpoint( ">480px" ) { - margin: 0 4px 0 8px; - text-align: center; - width: auto; - - .is-dismissible & { - margin-top: 40px; - } - - .dops-banner__prices { - justify-content: flex-end; - text-align: right; - } - } -} diff --git a/_inc/client/components/button-group/README.md b/_inc/client/components/button-group/README.md deleted file mode 100644 index ff31ca28c0227..0000000000000 --- a/_inc/client/components/button-group/README.md +++ /dev/null @@ -1,20 +0,0 @@ -Button Group -========= - -This component is used to group several semantically linked buttons under the same group - -#### How to use: - -```js -const ButtonGroup = require( 'components/button-group' ), - Button = require( 'components/button' ); - -render: function() { - return ( - - - - - ); -} -``` diff --git a/_inc/client/components/button-group/docs/example.jsx b/_inc/client/components/button-group/docs/example.jsx deleted file mode 100644 index a819e41bd259b..0000000000000 --- a/_inc/client/components/button-group/docs/example.jsx +++ /dev/null @@ -1,93 +0,0 @@ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/** - * External dependencies - */ -import React from 'react'; - -import PureRenderMixin from 'react-pure-render/mixin'; -import createReactClass from 'create-react-class'; - -/** - * Internal dependencies - */ -import ButtonGroup from 'components/button-group'; - -import Button from 'components/button'; -import Card from 'components/card'; -import Gridicon from 'components/gridicon'; - -const Buttons = createReactClass( { - displayName: 'ButtonGroup', - - mixins: [ PureRenderMixin ], - - getInitialState: function() { - return { - compact: false, - }; - }, - - toggleButtons: function() { - this.setState( { compact: ! this.state.compact } ); - }, - - render: function() { - return ( -
- - { this.state.compact ? 'Normal Buttons' : 'Compact Buttons' } - - -
- - - - -
-
- - - - - -
-
- - - - - - -
-
- - - - -
-
-
- ); - }, -} ); - -export default Buttons; diff --git a/_inc/client/components/button-group/index.jsx b/_inc/client/components/button-group/index.jsx deleted file mode 100644 index 176e942a512c3..0000000000000 --- a/_inc/client/components/button-group/index.jsx +++ /dev/null @@ -1,29 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import classNames from 'classnames'; - -import './style.scss'; - -export default class ButtonGroup extends React.Component { - static displayName = 'ButtonGroup'; - - static propTypes = { - children( props ) { - let error = null; - React.Children.forEach( props.children, child => { - if ( ! child.props || child.props.type !== 'button' ) { - error = new Error( 'All children elements should be a Button.' ); - } - } ); - return error; - }, - }; - - render() { - const buttonGroupClasses = classNames( 'dops-button-group', this.props.className ); - - return { this.props.children }; - } -} diff --git a/_inc/client/components/button-group/style.scss b/_inc/client/components/button-group/style.scss deleted file mode 100644 index 5b7fb86ca76f4..0000000000000 --- a/_inc/client/components/button-group/style.scss +++ /dev/null @@ -1,43 +0,0 @@ -@import "../../scss/calypso-colors"; - -.dops-button-group { - .dops-button { - border-left-width: 0; - border-radius: 0; - - &:focus { - // fixes focus styles in stacking context - position: relative; - z-index: z-index( 'button-group-parent', '.button-group .button:focus' ); - } - &.is-primary:focus { - box-shadow: - 0 0 0 1px $white, - 0 0 0 3px $blue-medium-dark; - } - &.is-scary:focus { - box-shadow: inset 1px 0 0 $alert-red, 0 0 0 2px lighten( $alert-red, 20% ); - } - &.is-primary.is-scary:focus { - box-shadow: inset 1px 0 0 darken( $alert-red, 30% ), 0 0 0 2px lighten( $alert-red, 20% ); - } - &.is-scary:first-child:focus { - box-shadow: 0 0 0 2px lighten( $alert-red, 20% ); - } - } - - .dops-button:first-child { - border-left-width: 1px; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - } - - .dops-button:last-child { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - } - - .dops-section-header & .dops-button { - margin-right: 0; - } -} diff --git a/_inc/client/components/button-group/test/index.js b/_inc/client/components/button-group/test/index.js deleted file mode 100644 index a0900d686f626..0000000000000 --- a/_inc/client/components/button-group/test/index.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * External dependencies - */ -import { assert } from 'chai'; -import React from 'react'; -import { shallow } from 'enzyme'; -import sinon from 'sinon'; - -describe( 'ButtonGroup', function() { - let sandbox, ButtonGroup, Button; - - beforeEach( function() { - sandbox = sinon.sandbox.create(); - sandbox.stub( console, 'error' ); - sandbox.stub( console, 'log' ); - - ButtonGroup = require( '../index' ); - Button = require( 'components/button' ); - } ); - - afterEach( function() { - sandbox.restore(); - } ); - - it( 'should have ButtonGroup class', function() { - const buttonGroup = shallow( ); - assert.equal( 1, buttonGroup.find( '.button-group' ).length ); - } ); - - it( 'should contains the same number of .button nodes than ); - assert.equal( 2, buttonGroup.find( Button ).length ); - } ); - - it( 'should throw an error if any of the children is not a - ); -} -``` - -#### Props - -* `compact`: (bool) whether the button is compact or not. -* `primary`: (bool) whether the button is styled as a primary button. -* `scary`: (bool) whether the button has modified styling to warn users (delete, remove, etc). -* `href`: (string) if this property is added, it will use an `a` rather than a `button` element. -* `disabled`: (bool) whether the button should be in the disabled state. diff --git a/_inc/client/components/button/index.jsx b/_inc/client/components/button/index.jsx deleted file mode 100644 index d945554f0015b..0000000000000 --- a/_inc/client/components/button/index.jsx +++ /dev/null @@ -1,52 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React from 'react'; -import classNames from 'classnames'; -import { noop } from 'lodash'; - -/** - * Internal dependencies - */ -import './style.scss'; - -export default class Button extends React.Component { - static displayName = 'Button'; - - static propTypes = { - disabled: PropTypes.bool, - compact: PropTypes.bool, - primary: PropTypes.bool, - scary: PropTypes.bool, - type: PropTypes.string, - href: PropTypes.string, - onClick: PropTypes.func, - borderless: PropTypes.bool, - className: PropTypes.string, - }; - - static defaultProps = { - disabled: false, - type: 'button', - onClick: noop, - borderless: false, - }; - - render() { - const element = this.props.href ? 'a' : 'button'; - const { primary, compact, scary, borderless, className, ...props } = this.props; - - const buttonClasses = classNames( { - 'dops-button': true, - 'is-compact': compact, - 'is-primary': primary, - 'is-scary': scary, - 'is-borderless': borderless, - } ); - - props.className = classNames( className, buttonClasses ); - - return React.createElement( element, props, this.props.children ); - } -} diff --git a/_inc/client/components/button/style.scss b/_inc/client/components/button/style.scss deleted file mode 100644 index 9160a5a439cc3..0000000000000 --- a/_inc/client/components/button/style.scss +++ /dev/null @@ -1,205 +0,0 @@ -@import "../../scss/calypso-colors"; -@import "../../scss/color-functions"; - -// ========================================================================== -// Buttons -// ========================================================================== - -.dops-button { - background: $blue-grey-light; - border-color: $blue-medium-dark; - border-style: solid; - border-width: 1px; - color: $blue-medium-dark; - cursor: pointer; - display: inline-block; - margin: 0; - outline: 0; - overflow: hidden; - font-size: 14px; - text-overflow: ellipsis; - text-decoration: none; - vertical-align: top; - box-sizing: border-box; - font-size: 13px; - border-radius: 3px; - padding: 7px 14px 9px; - -webkit-appearance: none; - appearance: none; - - &:hover { - background: #f1f1f1; - border-color: $blue-grey-dark; - color: $blue-grey-dark; - } - &[disabled], - &:disabled { - color: lighten( $gray, 30% ); - background: $white; - border-color: lighten( $gray, 30% ); - cursor: default; - } - &:focus { - background: $white; - border-color: $blue-medium-dark; - box-shadow: 0 0 0 1px $blue-medium-dark; - } - &.is-compact { - padding: 0 10px; - line-height: 2; - - &:disabled { - color: lighten( $gray, 30% ); - } - .gridicon { - top: 4px; - margin-top: -8px; - } - // Make the left margin of the small plus icon visually less huge - .gridicons-plus-small { - margin-left: -4px; - } - // Reset the left margin if the button contains only the plus icon - .gridicons-plus-small:last-of-type { - margin-left: 0; - } - // Make plus icon nudged closer to adjacent icons for add-people and add-plugin type buttons - .gridicons-plus-small + .gridicon { - margin-left: -4px; - } - } - &.hidden { - display: none; - } - .gridicon { - position: relative; - top: 4px; - margin-top: -2px; - width: 18px; - height: 18px; - } -} - -// Primary buttons -.dops-button.is-primary { - background: $blue-medium; - border-color: $blue-medium; - color: $white; - - &:hover, - &:focus { - border-color: $blue-medium-dark; - background: $blue-medium-dark; - color: $white; - } - &:focus { - box-shadow: - 0 0 0 1px $white, - 0 0 0 3px $blue-medium-dark; - } - &[disabled], - &:disabled { - color: #66c6e4 !important; - background-color: #008ec2 !important; - border-color: #008ec2 !important; - box-shadow: none !important; - text-shadow: none !important; - cursor: default; - } - &.is-compact { - color: $white; - white-space: nowrap; - } -} - -// Scary buttons -.dops-button.is-scary { - color: $alert-red; - - &:hover, - &:focus { - border-color: $alert-red; - } - &:focus { - box-shadow: 0 0 0 2px lighten( $alert-red, 20% ); - } - &[disabled], - &:disabled { - color: lighten( $alert-red, 30% ); - border-color: lighten( $gray, 30% ); - } -} - -.dops-button.is-primary.is-scary { - background: $alert-red; - border-color: darken( $alert-red, 20% ); - color: $white; - - &:hover, - &:focus { - border-color: darken( $alert-red, 40% ); - } - &[disabled], - &:disabled { - background: lighten( $alert-red, 20% ); - border-color: tint( $alert-red, 30% ); - } -} - -.dops-button.is-borderless { - border: none; - color: darken( $gray, 10% ); - padding-left: 0; - padding-right: 0; - - &:hover { - color: $gray-dark; - } - - &:focus { - box-shadow: none; - } - - .dops-accessible-focus &:focus { - outline: thin dotted; - } - - .gridicon { - width: 24px; - height: 24px; - top: 6px; - } - - &[disabled], - &:disabled { - color: lighten( $gray, 30% ); - background: $white; - cursor: default; - - &:active { - border-width: 0; - } - } - &.is-scary { - color: $alert-red; - - &:hover, - &:focus { - color: darken( $alert-red, 20% ); - } - - &[disabled] { - color: lighten( $alert-red, 30% ); - } - } - - &.is-compact { - background: transparent; - border-radius: 0; - .gridicon { - width: 18px; - height: 18px; - top: 5px; - } - } -} diff --git a/_inc/client/components/card/compact.jsx b/_inc/client/components/card/compact.jsx deleted file mode 100644 index 7bfacab4dfb12..0000000000000 --- a/_inc/client/components/card/compact.jsx +++ /dev/null @@ -1,23 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { assign } from 'lodash'; -import classnames from 'classnames'; - -/** - * Internal dependencies - */ -import Card from 'components/card'; - -export default class CompactCard extends React.Component { - static displayName = 'CompactCard'; - - render() { - const props = assign( {}, this.props, { - className: classnames( this.props.className, 'is-compact' ), - } ); - - return { this.props.children }; - } -} diff --git a/_inc/client/components/card/index.jsx b/_inc/client/components/card/index.jsx deleted file mode 100644 index 576b86dfe8ec6..0000000000000 --- a/_inc/client/components/card/index.jsx +++ /dev/null @@ -1,140 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React from 'react'; -import classnames from 'classnames'; -import { assign, omit } from 'lodash'; - -/** - * Internal dependencies - */ -import Gridicon from '../gridicon'; - -import './style.scss'; - -class CardSection extends React.Component { - static propTypes = { - title: PropTypes.any, - vertical: PropTypes.any, - style: PropTypes.object, - className: PropTypes.string, - device: PropTypes.oneOf( [ 'desktop', 'tablet', 'phone' ] ), - }; - - static defaultProps = { vertical: null }; - - render() { - return ( -
- { this.props.title ? this._renderWithTitle() : this.props.children } -
- ); - } - - _renderWithTitle = () => { - const orientation = this.props.vertical ? 'vertical' : 'horizontal'; - const wrapperClassName = 'dops-card-section-orient-' + orientation; - - return ( -
-

- { this.props.title } -

-
- { this.props.children } -
-
- ); - }; -} - -class CardFooter extends React.Component { - render() { - return
{ this.props.children }
; - } -} - -class Card extends React.Component { - static propTypes = { - meta: PropTypes.any, - icon: PropTypes.string, - iconLabel: PropTypes.any, - iconColor: PropTypes.string, - style: PropTypes.object, - className: PropTypes.string, - href: PropTypes.string, - onClick: PropTypes.func, - title: PropTypes.string, - tagName: PropTypes.string, - target: PropTypes.string, - compact: PropTypes.bool, - children: PropTypes.node, - }; - - static defaultProps = { - iconColor: '#787878', - className: '', - tagName: 'div', - onClick: () => {}, - }; - - render() { - const className = classnames( 'dops-card', this.props.className, { - 'is-card-link': !! this.props.href, - 'is-compact': this.props.compact, - } ); - - const omitProps = [ 'compact', 'tagName', 'meta', 'iconColor' ]; - - let linkIndicator; - if ( this.props.href ) { - linkIndicator = ( - - ); - } else { - omitProps.push( 'href', 'target' ); - } - - let fancyTitle; - if ( this.props.title ) { - fancyTitle = ( -

- { this.props.title } - { this.props.meta && { this.props.meta } } - { ( this.props.icon || this.props.iconLabel ) && this._renderIcon() } -

- ); - } - - return React.createElement( - this.props.href ? 'a' : this.props.tagName, - assign( omit( this.props, omitProps ), { className } ), - linkIndicator, - fancyTitle, - this.props.children - ); - } - - _renderIcon = () => { - return ( - - { this.props.icon && ( - - ) } - { this.props.iconLabel } - - ); - }; -} - -Card.Section = CardSection; -Card.Footer = CardFooter; - -export default Card; diff --git a/_inc/client/components/card/style.scss b/_inc/client/components/card/style.scss deleted file mode 100644 index 42eed0ada7f1c..0000000000000 --- a/_inc/client/components/card/style.scss +++ /dev/null @@ -1,68 +0,0 @@ -@import '../../scss/layout'; -@import '../../scss/typography'; -@import '../../scss/calypso-colors'; -@import '../../scss/rem'; - -$title: #f9f9f9; -$meta: #AAAAAA; -$border: #DDDDDD; -$section-border: #DDDDDD; - -/* Card */ - -.dops-card { - display: block; - position: relative; - margin: 0 auto 10px auto; - padding: 16px; - box-sizing: border-box; - background: $white; - box-shadow: - 0 0 0 1px $light-gray-700, - 0 1px 1px 1px rgba(0,0,0,.04); - - @include clear-fix; - - @include breakpoint( ">480px" ) { - margin-bottom: 16px; - padding: 24px; - } - - // Compact Card - &.is-compact { - margin-bottom: 1px; - - @include breakpoint( ">480px" ) { - margin-bottom: 1px; - padding: 16px 24px; - } - } - - &.is-card-link { - padding-right: 48px; - } -} - -// Clickable Card -.dops-card__link-indicator { - color: lighten( $gray, 20% ); - display: block; - height: 100%; - position: absolute; - top: 0; - right: 16px; -} - -a.dops-card:hover { - .dops-card__link-indicator { - color: lighten( $gray, 10% ); - } -} - -a.dops-card:focus { - outline: 0; - - .dops-card__link-indicator { - color: $link-highlight; - } -} diff --git a/_inc/client/components/chart/README.md b/_inc/client/components/chart/README.md deleted file mode 100644 index 695f63526741a..0000000000000 --- a/_inc/client/components/chart/README.md +++ /dev/null @@ -1,50 +0,0 @@ -Chart -===== - -This module renders a dataset as an HTML-based chart representing the data. - -## Usage - -```js - -// require the component -var ElementChart = require( 'my-sites/chart' ); - -// And use it inline inside the render method of another component -render: function() { - return( - } data={ } barClick={ } /> - ); -} - -// Example Data Array - -[ { - 'label': , // x-axis label - 'value': , // bar value - 'nestedValue': , // nested bar value or null if no nested bar - 'className': , // classname(s) applied to bar container - 'data': , // any data that you want to have access to in the barClick callback - 'tooltipData': [ - { - label: , - value: , - link: , - icon: , - className: - } - ] -} ] - - -``` - -## Required Props - -* `loading` — Any truthy value indicates the chart is loading -* `data` — An array of data objects using the format outlined above - -## Optional Props -* `minTouchBarWidth` — _default: 42_ The minimum bar width on touch devices -* `minBarWidth` — _default: 15_ The minimum bar width on non-touch devices -* `barClick` - The function to be called when a bar is clicked on the chart, it is passed the entire data object of the bar \ No newline at end of file diff --git a/_inc/client/components/chart/bar-container.jsx b/_inc/client/components/chart/bar-container.jsx deleted file mode 100644 index ce6af3f4d26b4..0000000000000 --- a/_inc/client/components/chart/bar-container.jsx +++ /dev/null @@ -1,64 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; - -import React from 'react'; - -/** - * Internal dependencies - */ -import Bar from './bar'; - -import XAxis from './x-axis'; - -export default class ModuleChartBarContainer extends React.Component { - static displayName = 'ModuleChartBarContainer'; - - static propTypes = { - isTouch: PropTypes.bool, - data: PropTypes.array, - yAxisMax: PropTypes.number, - width: PropTypes.number, - barClick: PropTypes.func, - }; - - buildBars = max => { - const numberBars = this.props.data.length, - width = this.props.chartWidth, - barWidth = width / numberBars; - let tooltipPosition = 'bottom right'; - const bars = this.props.data.map( function( item, index ) { - const barOffset = barWidth * ( index + 1 ); - - if ( barOffset + 230 > width && barOffset + barWidth - 230 > 0 ) { - tooltipPosition = 'bottom left'; - } - - return ( - - ); - }, this ); - - return bars; - }; - - render() { - return ( -
-
{ this.buildBars( this.props.yAxisMax ) }
- -
- ); - } -} diff --git a/_inc/client/components/chart/bar.jsx b/_inc/client/components/chart/bar.jsx deleted file mode 100644 index f94b845a9c351..0000000000000 --- a/_inc/client/components/chart/bar.jsx +++ /dev/null @@ -1,181 +0,0 @@ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; - -import React from 'react'; -import classNames from 'classnames'; - -/** - * Internal dependencies - */ -import Tooltip from 'components/tooltip'; - -import Gridicon from 'components/gridicon'; - -export default class ModuleChartBar extends React.Component { - static displayName = 'ModuleChartBar'; - - static propTypes = { - isTouch: PropTypes.bool, - tooltipPosition: PropTypes.string, - className: PropTypes.string, - clickHandler: PropTypes.func, - data: PropTypes.object.isRequired, - max: PropTypes.number, - count: PropTypes.number, - }; - - state = { showPopover: false }; - - buildSections = () => { - const value = this.props.data.value, - max = this.props.max, - percentage = max ? Math.ceil( ( value / max ) * 10000 ) / 100 : 0, - remain = 100 - percentage, - remainFloor = Math.max( 1, Math.floor( remain ) ), - sections = [], - nestedValue = this.props.data.nestedValue, - spacerClassOptions = { - 'dops-chart__bar-section': true, - 'is-spacer': true, - 'is-ghost': 100 === remain && ! this.props.active, - }; - let nestedBar, nestedPercentage, nestedStyle; - - const remainStyle = { - height: remainFloor + '%', - }; - - sections.push( -
- ); - - const valueStyle = { - top: remainFloor + '%', - }; - - if ( nestedValue ) { - nestedPercentage = value ? Math.ceil( ( nestedValue / value ) * 10000 ) / 100 : 0; - - nestedStyle = { height: nestedPercentage + '%' }; - - nestedBar = ( -
- ); - } - - sections.push( -
- { nestedBar } -
- ); - - sections.push( -
- { this.props.label } -
- ); - - return sections; - }; - - clickHandler = () => { - if ( 'function' === typeof this.props.clickHandler ) { - this.props.clickHandler( this.props.data ); - } - }; - - mouseEnter = () => { - this.setState( { showPopover: true } ); - }; - - mouseLeave = () => { - this.setState( { showPopover: false } ); - }; - - renderTooltip = () => { - if ( - ! this.props.data.tooltipData || - ! this.props.data.tooltipData.length || - this.props.isTouch - ) { - return null; - } - - const { tooltipData } = this.props.data; - - const listItemElements = tooltipData.map( function( options, i ) { - const wrapperClasses = [ 'module-content-list-item' ]; - let gridiconSpan; - - if ( options.icon ) { - gridiconSpan = ; - } - - wrapperClasses.push( options.className ); - - return ( -
  • - - { options.value } - - { gridiconSpan } - { options.label } - - -
  • - ); - } ); - - return ( - -
      { listItemElements }
    -
    - ); - }; - - render() { - const count = this.props.count || 1; - const barClass = { 'dops-chart__bar': true }; - - if ( this.props.className ) { - barClass[ this.props.className ] = true; - } - - const barStyle = { - width: ( 1 / count ) * 100 + '%', - }; - - return ( -
    - { this.buildSections() } -
    -
    -
    - { this.renderTooltip() } -
    - ); - } -} diff --git a/_inc/client/components/chart/index.jsx b/_inc/client/components/chart/index.jsx deleted file mode 100644 index ab156f0b51a07..0000000000000 --- a/_inc/client/components/chart/index.jsx +++ /dev/null @@ -1,160 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { noop, throttle } from 'lodash'; -import { translate as __ } from 'i18n-calypso'; - -/** - * Internal dependencies - */ -import BarContainer from './bar-container'; -import { hasTouch } from 'lib/touch-detect'; -import './style.scss'; - -export default class ModuleChart extends React.Component { - static displayName = 'ModuleChart'; - - static propTypes = { - loading: PropTypes.bool, - data: PropTypes.array, - minTouchBarWidth: PropTypes.number, - minBarWidth: PropTypes.number, - barClick: PropTypes.func, - }; - - static defaultProps = { - minTouchBarWidth: 42, - minBarWidth: 15, - barClick: noop, - }; - - state = { - maxBars: 100, // arbitrarily high number. This will be calculated by resize method - width: 650, - }; - - // Add listener for window resize - componentDidMount() { - this.resize = throttle( this.resize, 400 ); - window.addEventListener( 'resize', this.resize ); - this.resize(); - } - - // Remove listener - componentWillUnmount() { - window.removeEventListener( 'resize', this.resize ); - } - - UNSAFE_componentWillReceiveProps( nextProps ) { - if ( this.props.loading && ! nextProps.loading ) { - this.resize(); - } - } - - resize = () => { - const node = this.refs.chart; - let width = node.clientWidth - 82, - maxBars; - - if ( hasTouch() ) { - width = width <= 0 ? 350 : width; // mobile safari bug with zero width - maxBars = Math.floor( width / this.props.minTouchBarWidth ); - } else { - maxBars = Math.floor( width / this.props.minBarWidth ); - } - - this.setState( { - maxBars: maxBars, - width: width, - } ); - }; - - getYAxisMax = values => { - const max = Math.max.apply( null, values ), - operand = Math.pow( 10, max.toString().length - 1 ); - let rounded = Math.ceil( ( max + 1 ) / operand ) * operand; - - if ( rounded < 10 ) { - rounded = 10; - } - - return rounded; - }; - - getData = () => { - let data = this.props.data; - - data = data.slice( 0 - this.state.maxBars ); - - return data; - }; - - getValues = () => { - let data = this.getData(); - - data = data.map( function( item ) { - return item.value; - }, this ); - - return data; - }; - - isEmptyChart = values => { - values = values.filter( function( value ) { - return value > 0; - }, this ); - - return values.length === 0; - }; - - render() { - const values = this.getValues(), - yAxisMax = this.getYAxisMax( values ), - data = this.getData(); - let emptyChart; - - // If we have an empty chart, show a message - // @todo this message needs to either use a or make a custom "chart__notice" class - if ( values.length && this.isEmptyChart( values ) ) { - emptyChart = ( -
    - - { __( 'No activity this period', { - context: 'Notice in the empty statistics chart', - } ) } - -
    - ); - } - - return ( -
    -
    -
    -
    -
    -
    -
    -
    - { new Number( 100000 ).toLocaleString() } -
    -
    { yAxisMax.toLocaleString() }
    -
    - { ( yAxisMax / 2 ).toLocaleString() } -
    -
    { 0 }
    -
    - - { emptyChart } -
    - ); - } -} diff --git a/_inc/client/components/chart/label.jsx b/_inc/client/components/chart/label.jsx deleted file mode 100644 index ee4d0365d4ca9..0000000000000 --- a/_inc/client/components/chart/label.jsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; - -import React from 'react'; - -export default class ModuleChartLabel extends React.Component { - static displayName = 'ModuleChartLabel'; - - static propTypes = { - width: PropTypes.number.isRequired, - x: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - }; - - render() { - const dir = 'left'; - const labelStyle = { - width: this.props.width + 'px', - }; - - labelStyle[ dir ] = this.props.x + 'px'; - - return ( -
    - { this.props.label } -
    - ); - } -} diff --git a/_inc/client/components/chart/legend.jsx b/_inc/client/components/chart/legend.jsx deleted file mode 100644 index afd61da33b664..0000000000000 --- a/_inc/client/components/chart/legend.jsx +++ /dev/null @@ -1,107 +0,0 @@ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; - -import React from 'react'; -import PureRenderMixin from 'react-pure-render/mixin'; -import createReactClass from 'create-react-class'; - -/** - * Internal dependencies - */ - -const LegendItem = createReactClass( { - displayName: 'ModuleChartLegendItem', - - mixins: [ PureRenderMixin ], - - propTypes: { - checked: PropTypes.bool.isRequired, - label: PropTypes.oneOfType( [ PropTypes.object, PropTypes.string ] ), - attr: PropTypes.string.isRequired, - changeHandler: PropTypes.func.isRequired, - }, - - clickHandler: function() { - this.props.changeHandler( this.props.attr ); - }, - - render: function() { - return ( -
  • - -
  • - ); - }, -} ); - -class Legend extends React.Component { - static displayName = 'ModuleChartLegend'; - - static propTypes = { - activeTab: PropTypes.object.isRequired, - tabs: PropTypes.array.isRequired, - activeCharts: PropTypes.array.isRequired, - availableCharts: PropTypes.array.isRequired, - clickHandler: PropTypes.func.isRequired, - }; - - onFilterChange = chartItem => { - this.props.clickHandler( chartItem ); - }; - - render() { - const legendColors = [ 'dops-chart__legend-color is-dark-blue' ], - activeTab = this.props.activeTab; - const legendItems = this.props.availableCharts.map( function( legendItem, index ) { - const colorClass = legendColors[ index ], - checked = -1 !== this.props.activeCharts.indexOf( legendItem ); - const tab = this.props.tabs - .filter( function( currentTab ) { - return currentTab.attr === legendItem; - } ) - .shift(); - - return ( - - ); - }, this ); - - return ( -
    -
      -
    • - - - { activeTab.label } - -
    • - { legendItems } -
    -
    - ); - } -} - -export default Legend; diff --git a/_inc/client/components/chart/style.scss b/_inc/client/components/chart/style.scss deleted file mode 100644 index 89063350f3b15..0000000000000 --- a/_inc/client/components/chart/style.scss +++ /dev/null @@ -1,491 +0,0 @@ -// Chart -// Life is a statistical anomaly - -@import '../../scss/calypso-colors'; -@import '../../scss/layout'; -@import '../../scss/rem'; -@import '../../scss/mixin_clear-fix'; -@import '../../scss/mixin_long-content-fade'; -@import '../../scss/z-index'; -@import '../../scss/mixin_icons'; - -.dops-chart { - position: relative; - box-sizing: border-box; - background-color: $white; - padding: 8px 0 8px 20px; -} - -// Y-axis - -// Y-axis markers (lines) -// 1: Corresponds to padding of chart - -.dops-chart .dops-chart__y-axis-markers { - position: absolute; - top: 8px; // 1 - left: 0; - right: 0; - height: 200px; -} - -.dops-chart .dops-chart__y-axis-marker { - position: absolute; - top: 0; - width: 100%; - height: 1px; - border-top: 1px solid lighten( $gray, 30% ); -} - -// Y-axis marker lines inside each chart__bar -// (This is needed so that bars overlap correctly) -.dops-chart__bar-marker { - z-index: z-index( 'root', '.dops-chart__bar-marker' ); - position: absolute; - top: 0; - width: 100%; - height: 1px; - border-top: 1px solid rgba( lighten( $gray, 30% ), .1 ); -} - -.dops-chart__bar-marker, -.dops-chart__y-axis-label, -.dops-chart .dops-chart__y-axis-marker { - &.is-fifty { - top: 50%; - } - - &.is-zero { - top: 100%; - } -} - -// Y-axis labels -// 1: matches Y-axis padding - -.dops-chart__y-axis { - position: relative; - float: right; - height: 200px; - padding: 0 20px 0 10px; - font-size: 11px; - color: darken( $gray, 10% ); - margin-bottom: 30px; -} - -.dops-chart__y-axis-label { - position: absolute; - top: 0; - right: 20px; // 1 - text-align: right; -} - -// For forcing the width of y-axis to the width of the label -.dops-chart__y-axis-width-fix { - color: $transparent; -} - -// X-axis -// 1: hides spaces between elements - -.dops-chart__x-axis { - position: relative; - font-size: 0; // 1 - padding: 5px 0; - min-height: 18px; - color: darken( $gray, 30% ); -} - -.dops-chart__x-axis-label { - position: absolute; - display: inline-block; - vertical-align: top; - font-size: 11px; - text-align: center; -} - -// X-axis label indicator -// (vertical thin grey bar) - -.dops-chart__x-axis-label::before { - content: ''; - display: block; - position: absolute; - top: -4px; - left: 50%; - margin-left: -.5px; - width: 1px; - height: 5px; - background: $gray-light; - background-image: linear-gradient(to bottom, $gray-light 0%, lighten( $gray, 20% ) 100%); -} - -// Bar wrapper -// 1: hides spaces between elements - -.dops-chart__bars { - position: relative; - font-size: 0; // 1 - height: 200px; - text-align: center; - overflow: hidden; - display: -ms-flex; - display: flex; -} - -// Individual bar -// 1: Needs to be relative so that the contained graphic bar has boundaries - -.dops-chart__bar { - text-align: center; - display: inline-block; - position: relative; // 1 - height: 200px; - -ms-flex-grow: 1; - flex-grow: 1; - -ms-flex-shrink: 1; - flex-shrink: 1; - - - &.is-weekend { - background-color: rgba( lighten( $gray, 30% ), .5 ); - } - - &:hover { - cursor: pointer; - background-color: rgba( lighten( $gray, 30% ), .3 ); - } - - &.is-selected { - cursor: default; - background-color: rgba( $orange-jazzy, .1 ); - } -} - -// Individual bar wrapper & misc -// 1: Positions the bar in the space as defined by .bar -// 2: Default value for top so bars grow when they get a new value -// (doesn't function correctly for period switching right now, not sure why) -// (also doesn't work because updating the DOM is so heavy it stalls all animations) - -.dops-chart__bar-section { - display: inline-block; - background-color: $blue-wordpress; - position: absolute; - top: 0; // 2 - right: 16%; // 1 - bottom: 0; // 1 - left: 16%; // 1 - z-index: z-index( 'root', '.dops-chart__bar-section' ); - - .dops-chart__bar:hover &.is-bar { - background-color: $blue-medium; - } - - .dops-chart__bar.is-selected &.is-bar { - background-color: $orange-jazzy; - } - - &.is-spacer { - z-index: z-index( 'root', '.dops-chart__bar-section.is-spacer' ); - background-color: $transparent; - } - - &.is-ghost::after { - content: ""; - display: block; - position: absolute; - top: 160px; - bottom: 0; - left: 0; - z-index: z-index( 'root', '.dops-chart__bar-section.is-ghost::after' ); - width: 100%; - height: 40px; - background-image: linear-gradient(to bottom, $transparent, rgba( lighten( $gray, 30% ), .5 ) ); // TODO: needs to use default color for gradient - - .dops-chart__bar:hover & { - display: none; - } - } -} - -.dops-chart__bar-section-inner { - background: darken( $blue-dark, 5% ); - position: absolute; - right: 23.33%; - bottom: 0; - left: 23.33%; - - .dops-chart__bar.is-selected & { - background-color: $orange-fire; - } -} - -// Chart legend (wrapper) -// 1: L/R matches padding of y-axis labels in chart - -.dops-chart__legend { - @include clear-fix; - margin-bottom: -8px; -} - -// Chart legend options (list) - -.dops-chart__legend .dops-chart__legend-options { - float: right; - color: lighten( $gray-dark, 20% ); - list-style-type: none; - margin: 0; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.1em; - - @include breakpoint( "<480px" ) { - width: 100%; - } - -} - -// Chart legend option (list item) - -.dops-chart__legend-option { - display: inline; - text-align: left; - - // Expand labels to create bigger touch targets - @include breakpoint( "<480px") { - width: 50%; - display: inline-block; - } -} - -// Chart legend label -// 1: 19/10px instead of 20/12px because it aligns better optically - -.dops-chart__legend-label { - display: inline-block; - padding: 12px 19px 10px 20px; // 1 - - &.is-selectable { - cursor: pointer; - - &:focus, - &:hover { - color: $link-highlight; - } - } - - @include breakpoint( "<480px" ) { - display: block; - } -} - -// Chart legend color -// 1: Needed to overvwrite form styles in main stylesheets -// 2: Make leftmost legend fit snugly up against the leftmost bars - -.dops-chart__legend-option .dops-chart__legend-color { - width: 10px; - height: 10px; - background: $blue-wordpress; - display: inline-block; - border-radius: 1px; - vertical-align: top; - margin: 3px 5px 3px 8px; // 1 -} - -@include breakpoint( "<480px" ) { - .dops-chart__legend-option:first-child .dops-chart__legend-color { - margin-left: 2px; // 2 - } -} - -.dops-chart__legend-color.is-dark-blue { - background: darken( $blue-dark, 5% ); -} - -// Chart legend checkbox - -.dops-chart__legend-option .dops-chart__legend-checkbox { - margin: 0; - float: none; - vertical-align: top; -} - -// Chart empty (message) -// A message displayed when there's absolutely no data to chart - -.dops-chart__empty { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - text-align: center; - font-size: 14px; - line-height: 24px; - clear: both; - z-index: z-index( 'root', '.dops-chart__empty' ); -} - -.dops-chart__empty_notice { - position: relative; - top: 97px; - padding: 11px 24px; - margin-bottom: 24px; - border-radius: 1px; - background: #fff; - box-sizing: border-box; - font-size: 14px; - line-height: 1.4285; - animation: appear .3s ease-in-out; - box-shadow: 0 0 0 1px transparentize( lighten( $gray, 20% ), .5 ), - 0 1px 2px lighten( $gray, 30% ); - - @include breakpoint( ">660px" ) { - padding: 13px 48px; - font-size: inherit; - - &::before { - @include noticons; - content: '\f456'; - position: absolute; - top: 23px; - left: 20px; - margin: -12px 0px 0 -8px; - font-size: 24px; - line-height: 1; - } - } -} - -// Chart tooltip - -.dops-chart__tooltip { - - .dops-popover__inner { - width: 230px; - text-align: left; - - ul { - @include clear-fix; - list-style: none; - margin: 0; - padding: 0; - - li { - font-size: 11px; - text-transform: uppercase; - font-weight: 100; - height: 24px; - letter-spacing: 0.1em; - border: 0; - margin-bottom: 0; - - .dops-wrapper { - display: block; - line-height: inherit; - line-height: 24px; - clear: both; - } - - .value { - text-align: right; - float: right; - min-width: 22px; - color: lighten( $gray, 20% ); - } - - .label { - display: block; - overflow: hidden; - word-break: break-all; - vertical-align: baseline; - } - - .gridicon { - vertical-align: middle; - margin-right: 6px; - margin-top: -3px; - } - } - } - } - - &.is-streak { - margin-top: -5px; - height: 35px; - - .dops-popover__arrow::before { - left: 85px; - top: 30px; - } - - .dops-popover__inner { - width: 160px; - position: relative; - top: -10px; - - li { - height: 14px; - - .label { - width: 100%; - float: left; - text-align: center; - - .rtl & { - font-size: 11px; - } - - .post-count { - font-weight: bold; - } - } - .value { - float: none; - } - } - } - } -} - -.dops-chart__tooltip .dops-module-content-list-item { - - &.is-date-label { - font-size: 11px; - margin-bottom: 2px; - text-transform: uppercase; - font-weight: bold; - border-bottom: 1px solid darken( $gray, 27% ); - padding-bottom: 2px; - } - - &.is-published-item { - - height: 19px; - - .label { - text-transform: none; - color: lighten( $gray, 20% ); - overflow: hidden; - letter-spacing: 0; - height: 19px; - } - - .value { - width: 0; - min-width: 0; - - &::before { - content: ''; - position: relative; - background-image: linear-gradient(to right, rgba(61, 89, 109, 0) 0%, rgba(61, 89, 109, 0.5), rgba(61, 89, 109, 1)); - left: -30px; - width: 30px; - height: 24px; - display: block; - } - } - } -} diff --git a/_inc/client/components/chart/x-axis.jsx b/_inc/client/components/chart/x-axis.jsx deleted file mode 100644 index 30458a4ae24d1..0000000000000 --- a/_inc/client/components/chart/x-axis.jsx +++ /dev/null @@ -1,97 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { throttle } from 'lodash'; - -/** - * Internal dependencies - */ -import Label from './label'; - -export default class ModuleChartXAxis extends React.Component { - static displayName = 'ModuleChartXAxis'; - - static propTypes = { - labelWidth: PropTypes.number.isRequired, - data: PropTypes.array.isRequired, - }; - - state = { - divisor: 1, - spacing: this.props.labelWidth, - }; - - // Add listener for window resize - componentDidMount() { - this.resizeThrottled = throttle( this.resize, 400 ); - window.addEventListener( 'resize', this.resizeThrottled ); - this.resize(); - } - - // Remove listener - componentWillUnmount() { - if ( this.resizeThrottled.cancel ) { - this.resizeThrottled.cancel(); - } - window.removeEventListener( 'resize', this.resizeThrottled ); - } - - UNSAFE_componentWillReceiveProps( nextProps ) { - this.resize( nextProps ); - } - - resize = nextProps => { - let props = this.props; - - const node = this.refs.axis; - - if ( nextProps && ! ( nextProps instanceof Event ) ) { - props = nextProps; - } - - /** - * Overflow needs to be hidden to calculate the desired width, - * but visible to display each labels' overflow :/ - */ - - node.style.overflow = 'hidden'; - const width = node.clientWidth; - node.style.overflow = 'visible'; - - const dataCount = props.data.length || 1; - const spacing = width / dataCount; - const labelWidth = props.labelWidth; - const divisor = Math.ceil( labelWidth / spacing ); - - this.setState( { - divisor: divisor, - spacing: spacing, - } ); - }; - - render() { - const data = this.props.data; - - const labels = data.map( function( item, index ) { - const x = index * this.state.spacing + ( this.state.spacing - this.props.labelWidth ) / 2; - const rightIndex = data.length - index - 1; - let label; - - if ( rightIndex % this.state.divisor === 0 ) { - label = ( -