├── .editorconfig
├── .eslintrc.json
├── .github
└── workflows
│ └── nightly-build.yml
├── .gitignore
├── .nvmrc
├── .stylelintrc.json
├── .travis.yml
├── LICENSE.TXT
├── Makefile
├── README.TXT
├── README.md
├── assets
├── banner-1544x500.jpg
├── banner-1544x500.png
├── banner-772x250.jpg
├── banner-772x250.png
├── icon-256x256.png
├── screenshot-1.gif
├── screenshot-2.jpg
├── screenshot-3.gif
├── screenshot-4.jpg
└── stylesheets
│ ├── applause.scss
│ ├── editor.scss
│ ├── feedback.scss
│ ├── nps.scss
│ ├── poll.scss
│ ├── shared
│ ├── animations.scss
│ └── variables.scss
│ └── vote.scss
├── babel.config.js
├── changelog.txt
├── client
├── apifetch
│ ├── index.js
│ └── middleware.js
├── applause.js
├── blocks
│ ├── applause
│ │ ├── attributes.js
│ │ ├── constants.js
│ │ ├── edit.js
│ │ ├── edit.scss
│ │ ├── index.js
│ │ ├── sidebar.js
│ │ ├── toolbar.js
│ │ └── util.js
│ ├── cs-embed
│ │ ├── attributes.js
│ │ ├── cs-domains.js
│ │ ├── edit.js
│ │ ├── edit.scss
│ │ ├── embed-loading.js
│ │ ├── embed-preview.js
│ │ ├── index.js
│ │ ├── save.js
│ │ ├── sidebar.js
│ │ ├── toolbar.js
│ │ └── variations.js
│ ├── feedback
│ │ ├── attributes.js
│ │ ├── constants.js
│ │ ├── edit.js
│ │ ├── edit.scss
│ │ ├── index.js
│ │ ├── sidebar.js
│ │ ├── toolbar.js
│ │ └── util.js
│ ├── nps
│ │ ├── attributes.js
│ │ ├── constants.js
│ │ ├── edit.js
│ │ ├── edit.scss
│ │ ├── index.js
│ │ ├── sidebar.js
│ │ ├── toolbar.js
│ │ └── util.js
│ ├── poll
│ │ ├── attributes.js
│ │ ├── constants.js
│ │ ├── edit-answer.js
│ │ ├── edit-answers.js
│ │ ├── edit-bar.js
│ │ ├── edit.js
│ │ ├── edit.scss
│ │ ├── hooks.js
│ │ ├── index.js
│ │ ├── sidebar.js
│ │ ├── subscriptions.js
│ │ ├── toolbar.js
│ │ ├── util.js
│ │ └── util.test.js
│ ├── vote-item
│ │ ├── attributes.js
│ │ ├── edit.js
│ │ ├── edit.scss
│ │ ├── index.js
│ │ └── sidebar.js
│ └── vote
│ │ ├── attributes.js
│ │ ├── constants.js
│ │ ├── edit.js
│ │ ├── edit.scss
│ │ ├── index.js
│ │ ├── sidebar.js
│ │ ├── toolbar.js
│ │ └── util.js
├── components
│ ├── applause
│ │ ├── animation.js
│ │ ├── index.js
│ │ └── style.scss
│ ├── block-alignment-control
│ │ ├── constants.js
│ │ ├── grid-button.js
│ │ ├── grid.js
│ │ ├── icon.js
│ │ ├── index.js
│ │ └── style.scss
│ ├── brand-link
│ │ ├── index.js
│ │ └── style.scss
│ ├── connect-to-crowdsignal
│ │ ├── index.js
│ │ └── style.scss
│ ├── dialog-wrapper
│ │ ├── index.js
│ │ └── style.scss
│ ├── editor-notice
│ │ ├── index.js
│ │ └── style.scss
│ ├── feedback
│ │ ├── form.js
│ │ ├── index.js
│ │ ├── popover.js
│ │ ├── style.scss
│ │ ├── submit.js
│ │ ├── toggle.js
│ │ └── util.js
│ ├── footer-branding
│ │ ├── index.js
│ │ └── style.scss
│ ├── icon
│ │ ├── applause.js
│ │ ├── border.js
│ │ ├── check-circle.js
│ │ ├── checklist-multiple-choice.js
│ │ ├── checklist-single-choice.js
│ │ ├── close-small.js
│ │ ├── close.js
│ │ ├── counter.js
│ │ ├── cslogo.js
│ │ ├── feedback.js
│ │ ├── nps.js
│ │ ├── pencil.js
│ │ ├── placement.js
│ │ ├── poll.js
│ │ ├── quiz.js
│ │ ├── signal.js
│ │ ├── size.js
│ │ ├── survey.js
│ │ ├── thank-you.js
│ │ ├── thumbs-down.js
│ │ ├── thumbs-up.js
│ │ ├── vote.js
│ │ └── warning-circle.js
│ ├── nps
│ │ ├── feedback.js
│ │ ├── index.js
│ │ ├── rating.js
│ │ └── style.scss
│ ├── poll
│ │ ├── answer-results.js
│ │ ├── answer.js
│ │ ├── closed-banner.js
│ │ ├── error-banner.js
│ │ ├── index.js
│ │ ├── results.js
│ │ ├── style.scss
│ │ ├── submit-message.js
│ │ ├── util.js
│ │ ├── util.test.js
│ │ └── vote.js
│ ├── promotional-tooltip
│ │ └── index.js
│ ├── retry-notice
│ │ └── index.js
│ ├── sidebar-promote
│ │ ├── index.js
│ │ └── style.scss
│ ├── signal-warning
│ │ └── index.js
│ ├── use-autosave
│ │ └── index.js
│ ├── use-numbered-title
│ │ └── index.js
│ ├── use-poll-duplicate-cleaner
│ │ └── index.js
│ ├── vote
│ │ ├── index.js
│ │ ├── style.scss
│ │ ├── util.js
│ │ └── vote-item.js
│ ├── with-client-id
│ │ └── index.js
│ ├── with-fallback-styles
│ │ ├── index.js
│ │ ├── style.scss
│ │ ├── util.js
│ │ └── util.test.js
│ ├── with-fixed-position
│ │ └── index.js
│ ├── with-fse-check
│ │ └── index.js
│ └── with-poll-base
│ │ └── index.js
├── cs-embed.js
├── data
│ ├── feedback
│ │ ├── edit.js
│ │ └── index.js
│ ├── hooks
│ │ ├── index.js
│ │ └── util.js
│ ├── nps
│ │ ├── edit.js
│ │ └── index.js
│ ├── poll
│ │ └── index.js
│ └── util.js
├── editor.js
├── feedback.js
├── lib
│ ├── mutation-observer
│ │ └── index.js
│ └── tracks.js
├── nps.js
├── poll.js
├── state
│ ├── account
│ │ ├── actions.js
│ │ └── reducer.js
│ ├── action-types.js
│ ├── actions.js
│ ├── index.js
│ └── reducer.js
└── vote.js
├── composer.json
├── composer.lock
├── crowdsignal-forms.php
├── docker
├── .dockerignore
├── Dockerfile
├── README.md
├── bin
│ ├── install.sh
│ ├── install_composer.sh
│ ├── multisite-convert.sh
│ ├── run.sh
│ ├── tail.sh
│ └── uninstall.sh
├── config
│ ├── apache_default
│ ├── htaccess
│ ├── htaccess-multi
│ ├── php.ini
│ ├── ssmtp.conf
│ └── wp-tests-config.php
├── data
│ └── mysql
│ │ └── .gitkeep
├── default.env
├── docker-compose.yml
├── logs
│ └── .gitkeep
├── mu-plugins
│ ├── .gitignore
│ ├── 0-force-crowdsignal-forms-api-keys.php
│ ├── 1-always-throw-cs-sync-errors.php
│ ├── avoid-plugin-deletion.php
│ └── debug.php
├── wordpress-develop
│ └── .gitkeep
└── wordpress
│ └── .gitkeep
├── docs
└── development-environment.md
├── images
└── cs_dashboard_teaser.png
├── includes
├── admin
│ ├── admin-styles.css
│ ├── class-admin-hooks.php
│ ├── class-crowdsignal-forms-admin-notices.php
│ ├── class-crowdsignal-forms-admin.php
│ ├── class-crowdsignal-forms-notice-icon.php
│ ├── class-crowdsignal-forms-settings.php
│ ├── class-crowdsignal-forms-setup.php
│ ├── index.php
│ └── views
│ │ ├── html-admin-dashboard-teaser.php
│ │ ├── html-admin-notice-core-setup.php
│ │ ├── html-admin-settings.php
│ │ ├── html-admin-setup-footer.php
│ │ ├── html-admin-setup-header.php
│ │ ├── html-admin-setup-step-1.php
│ │ ├── html-admin-setup-step-2.php
│ │ └── html-admin-setup-step-3.php
├── auth
│ ├── class-api-auth-provider-interface.php
│ ├── class-crowdsignal-forms-api-authenticator.php
│ └── class-default-api-auth-provider.php
├── class-autoloader.php
├── class-crowdsignal-forms.php
├── frontend
│ ├── blocks
│ │ ├── class-crowdsignal-forms-applause-block.php
│ │ ├── class-crowdsignal-forms-feedback-block.php
│ │ ├── class-crowdsignal-forms-nps-block.php
│ │ ├── class-crowdsignal-forms-poll-block.php
│ │ ├── class-crowdsignal-forms-vote-block.php
│ │ └── class-crowdsignal-forms-vote-item-block.php
│ ├── class-crowdsignal-forms-block.php
│ ├── class-crowdsignal-forms-blocks-assets.php
│ ├── class-crowdsignal-forms-blocks.php
│ └── index.php
├── gateways
│ ├── class-api-gateway-interface.php
│ ├── class-api-gateway.php
│ ├── class-canned-api-gateway.php
│ ├── class-post-poll-meta-gateway.php
│ └── index.php
├── index.php
├── logging
│ └── class-webservice-logger.php
├── models
│ ├── class-feedback-survey.php
│ ├── class-nps-survey.php
│ ├── class-poll-answer.php
│ ├── class-poll-settings.php
│ └── class-poll.php
├── rest-api
│ └── controllers
│ │ ├── class-account-controller.php
│ │ ├── class-feedback-controller.php
│ │ ├── class-nps-controller.php
│ │ ├── class-polls-controller.php
│ │ └── index.php
└── synchronization
│ ├── class-comment-sync-entity.php
│ ├── class-poll-block-synchronizer.php
│ ├── class-post-sync-entity.php
│ └── class-synchronizable-entity.php
├── index.php
├── languages
└── crowdsignal-forms.pot
├── package.json
├── phpcs.xml.dist
├── phpunit.xml
├── pnpm-lock.yaml
├── scripts
├── makepot.sh
├── package-for-release.sh
└── prepare-release.sh
├── tests-js
└── mocks
│ ├── blocks.js
│ └── i18n.js
├── tests
├── .keep
├── README.md
├── bin
│ └── install.sh
├── bootstrap.php
├── canned-data
│ ├── api-data.json
│ └── block-data-empty.json
├── framework
│ └── class-crowdsignal-forms-unit-test-case.php
└── unit-tests
│ └── includes
│ ├── admin
│ └── test-class.admin-hooks.php
│ ├── auth
│ └── test-class.crowdsignal-forms-api-auth-test.php
│ ├── gateways
│ ├── test-class.api-gateway-interface.php
│ ├── test-class.api-gateway.php
│ └── test-class.canned-api-gateway.php
│ ├── models
│ ├── test-class.poll-settings.php
│ └── test-class.poll.php
│ └── rest-api
│ └── controllers
│ ├── test-class.account-controller.php
│ └── test-class.polls-controller.php
├── uninstall.php
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | # WordPress Coding Standards
5 | # https://make.wordpress.org/core/handbook/coding-standards/
6 |
7 | root = true
8 |
9 | [*]
10 | charset = utf-8
11 | end_of_line = lf
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 | indent_style = tab
15 | indent_size = 4
16 |
17 | [*.yml]
18 | indent_style = space
19 | indent_size = 2
20 |
21 | [*.md]
22 | trim_trailing_whitespace = false
23 |
24 | [*.txt]
25 | end_of_line = crlf
26 |
27 | [Makefile]
28 | indent_style = tab
29 | indent_size = 4
30 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
3 | "rules": {
4 | "@wordpress/i18n-text-domain": [
5 | "error",
6 | {
7 | "allowedTextDomain": "crowdsignal-forms"
8 | }
9 | ]
10 | },
11 | "settings": {
12 | "import/resolver": {
13 | "webpack": {}
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | .idea
3 | *.code-workspace
4 | vendor
5 | .env
6 | node_modules
7 | build
8 | release
9 | ## docker stuff
10 | /docker/data/mysql/*
11 | !/docker/data/mysql/.gitkeep
12 | /docker/logs/*
13 | !/docker/logs/.gitkeep
14 | # Custom environment for docker containers
15 | /docker/.env
16 | /docker/wordpress-develop/*
17 | !/docker/wordpress-develop/.gitkeep
18 | /docker/wordpress/*
19 | !/docker/wordpress/.gitkeep
20 |
21 | #
22 |
23 | # General
24 | .DS_Store
25 | .AppleDouble
26 | .LSOverride
27 |
28 | # Icon must end with two \r
29 | Icon
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear in the root of a volume
35 | .DocumentRevisions-V100
36 | .fseventsd
37 | .Spotlight-V100
38 | .TemporaryItems
39 | .Trashes
40 | .VolumeIcon.icns
41 | .com.apple.timemachine.donotpresent
42 |
43 | # Directories potentially created on remote AFP share
44 | .AppleDB
45 | .AppleDesktop
46 | Network Trash Folder
47 | Temporary Items
48 | .apdisk
49 |
50 |
51 | #
52 |
53 |
54 | #
55 |
56 | *~
57 |
58 | # temporary files which can be created if a process still has a handle open of a deleted file
59 | .fuse_hidden*
60 |
61 | # KDE directory preferences
62 | .directory
63 |
64 | # Linux trash folder which might appear on any partition or disk
65 | .Trash-*
66 |
67 | # .nfs files are created when an open file is removed but is still being accessed
68 | .nfs*
69 |
70 |
71 | #
72 |
73 |
74 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18.13.0
2 | // v14.15.0
3 | // maybe install with 12.13.1 but run with 14.15.0
4 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-wordpress/scss"
3 | }
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: linux
2 | dist: bionic
3 |
4 | language: php
5 | php: 7.4
6 | node_js: 12.13.1
7 |
8 | branches:
9 | only:
10 | - master
11 | - /^release\/.*/
12 | - /^feature\/.*/
13 |
14 | cache:
15 | directories:
16 | - build
17 | - node_modules
18 | - vendor
19 |
20 | install:
21 | - composer install
22 | - npm install
23 |
24 | before_script:
25 | - bash tests/bin/install.sh crowdsignal_forms_tests root '' localhost latest true
26 |
27 | jobs:
28 | include:
29 | - stage: lint
30 | script: vendor/bin/phpcs --ignore="*/docker/*" .
31 | - stage: lint
32 | script: npm run lint:js
33 | - stage: lint
34 | script: npm run lint:styles
35 | - stage: test
36 | # script: vendor/bin/phpunit
37 | # - stage: test
38 | script: npm run test
39 | - stage: build
40 | script: npm run build:editor
41 | - stage: build
42 | script: npm run build:styles
43 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | # Set up and build the entire project
3 | all: install client
4 |
5 | # Install all project dependencies
6 | install: install-node install-php
7 |
8 | # Install Node dependencies
9 | install-node:
10 | pnpm install
11 |
12 | # Install PHP dependencies
13 | install-php:
14 | composer install
15 |
16 | # Build the frontend client
17 | client:
18 | pnpm build
19 |
20 | # Package for release
21 | release: clean-release client pot
22 | ./scripts/package-for-release.sh
23 |
24 | # Clean the build directory
25 | clean:
26 | rm -rf build
27 |
28 | clean-release: clean
29 | rm -rf release
30 |
31 | docker_build:
32 | docker-compose -f docker/docker-compose.yml build
33 |
34 | docker_up:
35 | docker-compose -f docker/docker-compose.yml up
36 |
37 | docker_up_d:
38 | docker-compose -f docker/docker-compose.yml up -d
39 |
40 | docker_stop:
41 | docker-compose -f docker/docker-compose.yml stop
42 |
43 | docker_down:
44 | docker-compose -f docker/docker-compose.yml down
45 |
46 | docker_sh:
47 | docker-compose -f docker/docker-compose.yml exec wordpress bash
48 |
49 | docker_sh_db:
50 | docker-compose -f docker/docker-compose.yml exec db bash
51 |
52 | docker_install:
53 | docker-compose -f docker/docker-compose.yml exec wordpress bash -c "/var/scripts/install.sh"
54 |
55 | docker_uninstall:
56 | docker-compose -f docker/docker-compose.yml exec wordpress bash -c "/var/scripts/uninstall.sh"
57 |
58 | phpunit:
59 | docker-compose -f docker/docker-compose.yml exec wordpress bash -c "cd /var/www/html/wp-content/plugins/crowdsignal-forms && WP_TESTS_DIR=/tmp/wordpress-develop/tests/phpunit ./vendor/bin/phpunit"
60 |
61 | phpcs:
62 | docker-compose -f docker/docker-compose.yml exec wordpress bash -c "cd /var/www/html/wp-content/plugins/crowdsignal-forms && ./vendor/bin/phpcs"
63 |
64 | phpcbf:
65 | docker-compose -f docker/docker-compose.yml exec wordpress bash -c "cd /var/www/html/wp-content/plugins/crowdsignal-forms && ./vendor/bin/phpcbf"
66 |
67 | composer:
68 | docker-compose -f docker/docker-compose.yml exec wordpress bash -c "cd /var/www/html/wp-content/plugins/crowdsignal-forms && composer install"
69 |
70 | pot:
71 | ./scripts/makepot.sh
72 |
73 | .PHONY: install install-node install-php client clean clean-release docker_up_d docker_build docker_up docker_down docker_stop docker_sh docker_install docker_uninstall phpunit phpcs phpcbf composer release pot
74 |
--------------------------------------------------------------------------------
/assets/banner-1544x500.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/assets/banner-1544x500.jpg
--------------------------------------------------------------------------------
/assets/banner-1544x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/assets/banner-1544x500.png
--------------------------------------------------------------------------------
/assets/banner-772x250.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/assets/banner-772x250.jpg
--------------------------------------------------------------------------------
/assets/banner-772x250.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/assets/banner-772x250.png
--------------------------------------------------------------------------------
/assets/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/assets/icon-256x256.png
--------------------------------------------------------------------------------
/assets/screenshot-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/assets/screenshot-1.gif
--------------------------------------------------------------------------------
/assets/screenshot-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/assets/screenshot-2.jpg
--------------------------------------------------------------------------------
/assets/screenshot-3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/assets/screenshot-3.gif
--------------------------------------------------------------------------------
/assets/screenshot-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/assets/screenshot-4.jpg
--------------------------------------------------------------------------------
/assets/stylesheets/applause.scss:
--------------------------------------------------------------------------------
1 | // Shared
2 | @import "./shared/animations.scss";
3 | @import "./shared/variables.scss";
4 |
5 | // Components
6 | @import "components/applause/style.scss";
7 | @import "components/brand-link/style.scss";
8 |
--------------------------------------------------------------------------------
/assets/stylesheets/editor.scss:
--------------------------------------------------------------------------------
1 | // Shared
2 | @import "./shared/variables.scss";
3 |
4 | // Components
5 | @import "components/block-alignment-control/style.scss";
6 | @import "components/connect-to-crowdsignal/style.scss";
7 | @import "components/editor-notice/style.scss";
8 | @import "components/sidebar-promote/style.scss";
9 | @import "components/footer-branding/style.scss";
10 |
11 | // Poll
12 | @import "blocks/poll/edit.scss";
13 | @import "components/poll/style.scss";
14 | @import "components/with-fallback-styles/style.scss";
15 |
16 | // Vote
17 | @import "blocks/vote/edit.scss";
18 | @import "blocks/vote-item/edit.scss";
19 | @import "components/vote/style.scss";
20 |
21 | // Applause
22 | @import "blocks/applause/edit.scss";
23 | @import "components/applause/style.scss";
24 |
25 | // NPS
26 | @import "blocks/nps/edit.scss";
27 | @import "components/nps/style.scss";
28 |
29 | // Feedback
30 | @import "blocks/feedback/edit.scss";
31 | @import "components/feedback/style.scss";
32 |
33 | // Embed
34 | @import "blocks/cs-embed/edit.scss"
--------------------------------------------------------------------------------
/assets/stylesheets/feedback.scss:
--------------------------------------------------------------------------------
1 | // Shared
2 | @import "./shared/animations.scss";
3 | @import "./shared/variables.scss";
4 |
5 | // Components
6 | @import "components/feedback/style.scss";
7 | @import "components/footer-branding/style.scss";
8 |
--------------------------------------------------------------------------------
/assets/stylesheets/nps.scss:
--------------------------------------------------------------------------------
1 | // Shared
2 | @import "./shared/animations.scss";
3 | @import "./shared/variables.scss";
4 |
5 | // Components
6 | @import "components/dialog-wrapper/style.scss";
7 | @import "components/nps/style.scss";
8 | @import "components/footer-branding/style.scss";
9 |
--------------------------------------------------------------------------------
/assets/stylesheets/poll.scss:
--------------------------------------------------------------------------------
1 | // Shared
2 | @import "./shared/animations.scss";
3 | @import "./shared/variables.scss";
4 |
5 | // Components
6 | @import "components/poll/style.scss";
7 | @import "components/with-fallback-styles/style.scss";
8 | @import "components/footer-branding/style.scss";
9 |
--------------------------------------------------------------------------------
/assets/stylesheets/shared/animations.scss:
--------------------------------------------------------------------------------
1 | @keyframes crowdsignal-forms-animation__pop {
2 |
3 | 0% {
4 | transform: scale(0);
5 | }
6 |
7 | 50% {
8 | transform: scale(1.2);
9 | }
10 |
11 | 100% {
12 | transform: scale(1);
13 | }
14 | }
15 |
16 | @keyframes crowdsignal-forms-animation__pulse {
17 |
18 | 0% {
19 | opacity: 0.4;
20 | }
21 |
22 | 50% {
23 | opacity: 0.7;
24 | }
25 |
26 | 100% {
27 | opacity: 0.4;
28 | }
29 | }
30 |
31 | @keyframes crowdsignal-forms-animation__grow {
32 |
33 | 0% {
34 | transform: scale(1);
35 | }
36 |
37 | 50% {
38 | transform: scale(1.4);
39 | }
40 |
41 | 100% {
42 | transform: scale(1);
43 | }
44 | }
45 |
46 | @keyframes crowdsignal-forms-animation__fade-in {
47 |
48 | 0% {
49 | opacity: 0;
50 | }
51 |
52 | 100% {
53 | opacity: 1;
54 | }
55 | }
56 |
57 | @keyframes crowdsignal-forms-animation__fade-out {
58 |
59 | 0% {
60 | opacity: 1;
61 | }
62 |
63 | 100% {
64 | opacity: 0;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/assets/stylesheets/shared/variables.scss:
--------------------------------------------------------------------------------
1 | $font-sans-serif: -apple-system, blinkmacsystemfont, "Segoe UI", roboto, oxygen-sans, ubuntu, cantarell, "Helvetica Neue", sans-serif;
2 | $font-size-gutenberg-system-default: 13px;
3 |
--------------------------------------------------------------------------------
/assets/stylesheets/vote.scss:
--------------------------------------------------------------------------------
1 | // Shared
2 | @import "./shared/animations.scss";
3 | @import "./shared/variables.scss";
4 |
5 | // Components
6 | @import "components/vote/style.scss";
7 | @import "components/brand-link/style.scss";
8 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [ '@wordpress/default' ],
3 | };
4 |
--------------------------------------------------------------------------------
/client/apifetch/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { findIndex, forEach } from 'lodash';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import { defaultHeaders, formatURL, formatRequest, wpAuth } from './middleware';
10 |
11 | /**
12 | * Middlewares to be applied to the apiFetch call.
13 | */
14 | const middlewareRegistry = [];
15 |
16 | /**
17 | * Applies all middlewares and runs the request.
18 | *
19 | * @param {Object} options Passed as the second param for window.fetch
20 | * @param {string} options.path Request URL
21 | * @param {Function} apply The middleware to apply next
22 | * @return {Promise} Request promise
23 | */
24 | const run = async ( { path, ...options }, [ apply, ...middlewares ] ) => {
25 | if ( ! apply ) {
26 | const response = await window.fetch( path, options );
27 |
28 | return response.json();
29 | }
30 |
31 | return apply(
32 | {
33 | path,
34 | ...options,
35 | },
36 | ( nextOptions ) => run( nextOptions, middlewares )
37 | );
38 | };
39 |
40 | /**
41 | * Makes a request using window.fetch and registered middleware.
42 | *
43 | * @param {Object} options Request options
44 | * @param {string} options.path Request URL (required)
45 | * @return {Promise} Request promise
46 | */
47 | const apiFetch = async ( options ) => run( options, middlewareRegistry );
48 |
49 | /**
50 | * Appends a middleware to apiFetch
51 | *
52 | * @param {Function} middleware Middleware function
53 | */
54 | apiFetch.use = ( middleware ) => middlewareRegistry.push( middleware );
55 |
56 | /**
57 | * Removes a middleware from apiFetch
58 | *
59 | * @param {Function} middleware The middleware to remove
60 | */
61 | apiFetch.disable = ( middleware ) => {
62 | const index = findIndex( middlewareRegistry, ( m ) => m === middleware );
63 |
64 | if ( index ) {
65 | middlewareRegistry.splice( index, 1 );
66 | }
67 | };
68 |
69 | /**
70 | * Export the default middlewares on the apiFetch object
71 | *
72 | * @type {Object}
73 | */
74 | apiFetch.middleware = {
75 | defaultHeaders,
76 | formatURL,
77 | formatRequest,
78 | wpAuth,
79 | };
80 |
81 | /**
82 | * Apply the default middlewares
83 | */
84 | forEach( apiFetch.middleware, apiFetch.use );
85 |
86 | export default apiFetch;
87 |
--------------------------------------------------------------------------------
/client/apifetch/middleware.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Prefixes the request URLs with '/wp-json'
3 | *
4 | * @param {Object} options Request options
5 | * @param {Function} next Next middleware
6 | * @return {Promise} Request promsie
7 | */
8 | export const formatURL = ( options, next ) => {
9 | if ( options.path.indexOf( '/crowdsignal-forms/v1' ) === 0 ) {
10 | options.path = _crowdsignalFormsURL + `/wp-json${ options.path }`;
11 | }
12 |
13 | return next( options );
14 | };
15 |
16 | /**
17 | * Set default headers
18 | *
19 | * @param {Object} options Request options
20 | * @param {Function} next Next middleware
21 | * @return {Promise} Request promsie
22 | */
23 | export const defaultHeaders = ( options, next ) => {
24 | const headers = options.headers || {};
25 |
26 | return next( {
27 | ...options,
28 | headers: {
29 | ...headers,
30 | // The backend uses the Accept header as a condition for considering an
31 | // incoming request as a REST request.
32 | //
33 | // See: https://core.trac.wordpress.org/ticket/44534
34 | Accept: 'application/json, */*;q=0.1',
35 | },
36 | } );
37 | };
38 |
39 | /**
40 | * Convert data to JSON
41 | *
42 | * @param {Object} options Request options
43 | * @param {Object} options.data Request data
44 | * @param {Function} next Next middleware
45 | * @return {Promise} Request promsie
46 | */
47 | export const formatRequest = ( { data, ...options }, next ) => {
48 | if ( ! data ) {
49 | return next( options );
50 | }
51 |
52 | return next( {
53 | ...options,
54 | headers: {
55 | ...options.headers,
56 | 'Content-Type': 'application/json',
57 | },
58 | body: JSON.stringify( data ),
59 | } );
60 | };
61 |
62 | /**
63 | * Auth middleware.
64 | * Detects whether the current user is logged in and if so, include credentials in the request.
65 | *
66 | * @param {Object} options Request options
67 | * @param {Function} next Next middleware
68 | * @return {Promise} Request promsie
69 | */
70 | export const wpAuth = ( options, next ) => {
71 | if ( ! window._crowdsignalFormsWpNonce ) {
72 | return next( options );
73 | }
74 |
75 | return next( {
76 | credentials: 'same-origin',
77 | mode: 'same-origin',
78 | ...options,
79 | headers: {
80 | 'X-WP-Nonce': window._crowdsignalFormsWpNonce,
81 | ...options.headers,
82 | },
83 | } );
84 | };
85 |
--------------------------------------------------------------------------------
/client/applause.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import Applause from 'components/applause';
10 | import MutationObserver from 'lib/mutation-observer';
11 |
12 | MutationObserver( 'data-crowdsignal-applause', ( attributes ) => (
13 |
14 | ) );
15 |
--------------------------------------------------------------------------------
/client/blocks/applause/attributes.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Note: Any changes made to the attributes definition need to be duplicated in
3 | * Crowdsignal_Forms\Frontend\Blocks\Crowdsignal_Forms_Applause_Block::attributes()
4 | * inside includes/frontend/blocks/class-crowdsignal-forms-applause-block.php.
5 | */
6 |
7 | import { PollStatus } from './constants';
8 |
9 | export default {
10 | pollId: {
11 | type: 'string',
12 | default: null,
13 | },
14 | hideBranding: {
15 | type: 'boolean',
16 | default: false,
17 | },
18 | title: {
19 | type: 'string',
20 | default: null,
21 | },
22 | answerId: {
23 | type: 'string',
24 | default: null,
25 | },
26 | size: {
27 | type: 'string',
28 | default: 'medium',
29 | },
30 | pollStatus: {
31 | type: 'string',
32 | default: PollStatus.OPEN,
33 | },
34 | closedAfterDateTime: {
35 | type: 'string',
36 | default: null,
37 | },
38 | textColor: {
39 | type: 'string',
40 | },
41 | backgroundColor: {
42 | type: 'string',
43 | },
44 | borderColor: {
45 | type: 'string',
46 | },
47 | borderWidth: {
48 | type: 'number',
49 | default: 0,
50 | },
51 | borderRadius: {
52 | type: 'number',
53 | default: 0,
54 | },
55 | };
56 |
--------------------------------------------------------------------------------
/client/blocks/applause/constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | export const PollStatus = Object.freeze( {
7 | OPEN: 'open',
8 | CLOSED: 'closed',
9 | CLOSED_AFTER: 'closed-after',
10 | } );
11 |
12 | export const DEFAULT_SIZE_CONTROLS = [
13 | {
14 | title: __( 'Small', 'crowdsignal-forms' ),
15 | size: 'small',
16 | },
17 | {
18 | title: __( 'Medium', 'crowdsignal-forms' ),
19 | size: 'medium',
20 | },
21 | {
22 | title: __( 'Large', 'crowdsignal-forms' ),
23 | size: 'large',
24 | },
25 | ];
26 |
27 | export const POPOVER_PROPS = {
28 | position: 'bottom right',
29 | isAlternate: true,
30 | className: 'crowdsignal-forms-vote__size-dropdown',
31 | };
32 |
--------------------------------------------------------------------------------
/client/blocks/applause/edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 | import { get } from 'lodash';
6 |
7 | /**
8 | * WordPress dependencies
9 | */
10 | import { compose } from '@wordpress/compose';
11 | import { __ } from '@wordpress/i18n';
12 | import { useSelect } from '@wordpress/data';
13 |
14 | /**
15 | * Internal dependencies
16 | */
17 | import ConnectToCrowdsignal from 'components/connect-to-crowdsignal';
18 | import withClientId from 'components/with-client-id';
19 | import useNumberedTitle from 'components/use-numbered-title';
20 | import Applause from 'components/applause';
21 | import withPollBase from 'components/with-poll-base';
22 | import Toolbar from './toolbar';
23 | import SideBar from './sidebar';
24 | import { STORE_NAME } from 'state';
25 | import withFseCheck from 'components/with-fse-check';
26 |
27 | const EditApplauseBlock = ( props ) => {
28 | const { attributes, setAttributes, pollDataFromApi } = props;
29 |
30 | const viewResultsUrl = pollDataFromApi
31 | ? pollDataFromApi.viewResultsUrl
32 | : '';
33 |
34 | useNumberedTitle(
35 | props.name,
36 | __( 'Untitled Applause', 'crowdsignal-forms' ),
37 | attributes,
38 | setAttributes
39 | );
40 |
41 | const accountInfo = useSelect( ( select ) =>
42 | select( STORE_NAME ).getAccountInfo()
43 | );
44 |
45 | const shouldPromote = get( accountInfo, [
46 | 'signalCount',
47 | 'shouldDisplay',
48 | ] );
49 | const signalWarning =
50 | shouldPromote &&
51 | get( accountInfo, [ 'signalCount', 'count' ] ) >=
52 | get( accountInfo, [ 'signalCount', 'userLimit' ] );
53 |
54 | return (
55 |
59 |
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | export default compose( [
72 | withFseCheck,
73 | withPollBase,
74 | withClientId( [ 'pollId', 'answerId' ] ),
75 | ] )( EditApplauseBlock );
76 |
--------------------------------------------------------------------------------
/client/blocks/applause/edit.scss:
--------------------------------------------------------------------------------
1 | /* editor styles */
2 |
--------------------------------------------------------------------------------
/client/blocks/applause/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import ApplauseIcon from 'components/icon/applause';
10 | import EditApplauseBlock from './edit';
11 | import attributes from './attributes';
12 |
13 | export default {
14 | title: __( 'Applause', 'crowdsignal-forms' ),
15 | description: __(
16 | 'Let your audience cheer with a big round of applause — powered by Crowdsignal.',
17 | 'crowdsignal-forms'
18 | ),
19 | category: 'crowdsignal-forms',
20 | keywords: [
21 | 'crowdsignal',
22 | __( 'applause', 'crowdsignal-forms' ),
23 | __( 'cheer', 'crowdsignal-forms' ),
24 | __( 'cheering', 'crowdsignal-forms' ),
25 | __( 'clap', 'crowdsignal-forms' ),
26 | __( 'feedback', 'crowdsignal-forms' ),
27 | __( 'kudos', 'crowdsignal-forms' ),
28 | __( 'like', 'crowdsignal-forms' ),
29 | __( 'opinion', 'crowdsignal-forms' ),
30 | __( 'praise', 'crowdsignal-forms' ),
31 | __( 'rating', 'crowdsignal-forms' ),
32 | __( 'upvote', 'crowdsignal-forms' ),
33 | __( 'upvoting', 'crowdsignal-forms' ),
34 | __( 'votes', 'crowdsignal-forms' ),
35 | __( 'voting', 'crowdsignal-forms' ),
36 | ],
37 | icon: ,
38 | edit: EditApplauseBlock,
39 | attributes,
40 | usesContext: [ 'postId', 'queryId' ],
41 | example: {
42 | attributes: {
43 | size: 'large',
44 | },
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/client/blocks/applause/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import classNames from 'classnames';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import { isEmpty, kebabCase, mapKeys } from 'lodash';
10 |
11 | export const getApplauseStyleVars = ( attributes, fallbackStyles ) => {
12 | const textColor = isEmpty( attributes.textColor )
13 | ? fallbackStyles.textColor
14 | : attributes.textColor;
15 |
16 | return mapKeys(
17 | {
18 | bgColor:
19 | attributes.backgroundColor || fallbackStyles.backgroundColor,
20 | textColor,
21 | hoverColor: fallbackStyles.accentColor,
22 | borderRadius: `${ attributes.borderRadius || 0 }px`,
23 | borderWidth: `${ attributes.borderWidth || 0 }px`,
24 | borderColor: attributes.borderColor,
25 | },
26 | ( _, key ) => `--crowdsignal-forms-applause-${ kebabCase( key ) }`
27 | );
28 | };
29 |
30 | /**
31 | * Returns a css 'class' string of overridden styles given a collection of attributes.
32 | *
33 | * @param {*} attributes The block's attributes
34 | * @param {...any} extraClasses A list of additional classes to add to the class string
35 | */
36 | export const getBlockCssClasses = ( attributes, ...extraClasses ) => {
37 | return classNames(
38 | {
39 | 'has-bg-color': attributes.backgroundColor,
40 | 'has-text-color': attributes.textColor,
41 | 'has-border-color': attributes.borderColor,
42 | },
43 | extraClasses
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/attributes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 | import { createInterpolateElement } from '@wordpress/element';
6 |
7 | export default {
8 | url: {
9 | type: 'string',
10 | },
11 | caption: {
12 | type: 'string',
13 | source: 'html',
14 | selector: 'figcaption',
15 | },
16 | type: {
17 | type: 'string',
18 | default: 'html',
19 | },
20 | providerNameSlug: {
21 | type: 'string',
22 | default: 'crowdsignal',
23 | },
24 | allowResponsive: {
25 | type: 'boolean',
26 | default: true,
27 | },
28 | responsive: {
29 | type: 'boolean',
30 | default: false,
31 | },
32 | previewable: {
33 | type: 'boolean',
34 | default: true,
35 | },
36 | createLink: {
37 | type: 'string',
38 | default:
39 | 'https://crowdsignal.com/support/add-a-multipage-survey-to-any-wordpress-page-or-post/?ref=surveyembedblock',
40 | },
41 | createText: {
42 | type: 'string',
43 | default: __( 'Create a new Survey', 'crowdsignal-forms' ),
44 | },
45 | typeText: {
46 | type: 'string',
47 | default: __( 'survey', 'crowdsignal-forms' ),
48 | },
49 | editText: {
50 | type: 'string',
51 | default: createInterpolateElement(
52 | __(
53 | 'Edit your surveys on crowdsignal.com ',
54 | 'crowdsignal-forms'
55 | ),
56 | {
57 | a: (
58 | // eslint-disable-next-line jsx-a11y/anchor-has-content
59 |
64 | ),
65 | }
66 | ),
67 | },
68 | dashboardLink: {
69 | type: 'string',
70 | default: 'https://app.crowdsignal.com/?ref=surveyembedblock',
71 | },
72 | embedMessage: {
73 | type: 'string',
74 | default: __(
75 | 'Paste a link to the survey you want to display on your site',
76 | 'crowdsignal-forms'
77 | ),
78 | },
79 | placeholderTitle: {
80 | type: 'string',
81 | default: __( 'Survey Embed', 'crowdsignal-forms' ),
82 | },
83 | };
84 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/cs-domains.js:
--------------------------------------------------------------------------------
1 | // an array of domains that are valid for CS embeds
2 | export default [ 'crowdsignal.com', 'survey.fm', 'crowdsignal.net' ];
3 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/edit.scss:
--------------------------------------------------------------------------------
1 | /* Editor styles */
2 |
3 | #editor .editor-styles-wrapper {
4 |
5 | .crowdsignal-logo {
6 | height: 40px;
7 | width: 40px;
8 | }
9 |
10 | .cs-embed__button {
11 | margin-left: 8px;
12 | }
13 |
14 | .cs-embed__field {
15 | width: 85%;
16 | }
17 |
18 | .cs-embed__error {
19 | color: red;
20 | padding-left: 4px;
21 | }
22 |
23 | .cs-embed__instructions {
24 | padding-bottom: 16px;
25 | padding-left: 4px;
26 | }
27 |
28 | .cs-embed__create-link {
29 | padding-top: 16px;
30 | padding-left: 4px;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/embed-loading.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Spinner } from '@wordpress/components';
5 |
6 | const EmbedLoading = () => (
7 |
8 |
9 |
10 | );
11 |
12 | export default EmbedLoading;
13 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/embed-preview.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { SandBox } from '@wordpress/components';
5 | import { useBlockProps } from '@wordpress/block-editor';
6 |
7 | export default function EmbedPreview( { html } ) {
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import Survey from '../../components/icon/survey';
10 | import EditEmbedBlock from './edit';
11 | import SaveEmbedBlock from './save';
12 | import attributes from './attributes';
13 | import variations from './variations';
14 |
15 | export default {
16 | title: __( 'Survey', 'crowdsignal-forms' ),
17 | description: __(
18 | 'Create a multipage survey on crowdsignal.com and embed it.',
19 | 'crowdsignal-forms'
20 | ),
21 | category: 'crowdsignal-forms',
22 | keywords: [ __( 'survey', 'crowdsignal-forms' ) ],
23 | icon: ,
24 | edit: EditEmbedBlock,
25 | save: SaveEmbedBlock,
26 | variations,
27 | attributes,
28 | supports: {
29 | align: [ 'center', 'wide', 'full' ],
30 | },
31 | getEditWrapperProps: ( { align } ) => ( {
32 | 'data-align': align,
33 | } ),
34 | };
35 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/save.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import classnames from 'classnames/dedupe';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import { useBlockProps } from '@wordpress/block-editor';
10 |
11 | export default function save( { attributes } ) {
12 | const { url, type, providerNameSlug } = attributes;
13 | if ( ! url ) {
14 | return null;
15 | }
16 | const className = classnames( 'wp-block-embed', {
17 | [ `is-type-${ type }` ]: type,
18 | [ `is-provider-${ providerNameSlug }` ]: providerNameSlug,
19 | [ `wp-block-embed-${ providerNameSlug }` ]: providerNameSlug,
20 | } );
21 | return (
22 |
23 |
24 | { `\n${ url }\n` /* URL needs to be on its own line. */ }
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/sidebar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import { Button, PanelBody, PanelRow } from '@wordpress/components';
10 | import { InspectorControls } from '@wordpress/block-editor';
11 | import { __ } from '@wordpress/i18n';
12 | import { createInterpolateElement } from '@wordpress/element';
13 |
14 | /**
15 | * Internal dependencies
16 | */
17 | import SidebarPromote from 'components/sidebar-promote';
18 |
19 | const Sidebar = ( { attributes, shouldPromote, signalWarning } ) => {
20 | const { editText, createText, dashboardLink } = attributes;
21 | return (
22 |
23 |
27 | { editText }
28 |
29 |
35 |
36 |
37 |
41 |
42 | { createInterpolateElement(
43 | __(
44 | 'View results on
crowdsignal.com ',
45 | 'crowdsignal-forms'
46 | ),
47 | {
48 | a: (
49 | // eslint-disable-next-line jsx-a11y/anchor-has-content
50 |
55 | ),
56 | }
57 | ) }
58 |
59 |
60 |
61 |
67 |
68 |
69 |
70 |
71 | { shouldPromote && (
72 |
73 |
74 |
75 |
76 |
77 | ) }
78 |
79 | );
80 | };
81 |
82 | export default Sidebar;
83 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/toolbar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 | import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
6 | import { BlockControls } from '@wordpress/block-editor';
7 | /**
8 | * Local dependencies
9 | */
10 | import Pencil from '../../components/icon/pencil';
11 |
12 | const ToolbarControls = ( { setIsEditingURL } ) => (
13 | <>
14 |
15 |
16 | {
21 | setIsEditingURL( true );
22 | } }
23 | />
24 |
25 |
26 | >
27 | );
28 |
29 | export default ToolbarControls;
30 |
--------------------------------------------------------------------------------
/client/blocks/cs-embed/variations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 | import { createInterpolateElement } from '@wordpress/element';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import QuizIcon from '../../components/icon/quiz';
11 | export default [
12 | {
13 | name: 'crowdsignal-forms/quiz',
14 | isDefault: false,
15 | title: __( 'Quiz', 'crowdsignal-forms' ),
16 | description: __(
17 | 'Create a multipage quiz on crowdsignal.com and embed it.',
18 | 'crowdsignal-forms'
19 | ),
20 | icon: ,
21 | attributes: {
22 | createLink:
23 | 'https://crowdsignal.com/support/create-a-quiz/?ref=quizembedblock',
24 | createText: __( 'Create a new Quiz', 'crowdsignal-forms' ),
25 | placeholderTitle: __( 'Quiz Embed', 'crowdsignal-forms' ),
26 | typeText: __( 'quiz', 'crowdsignal-forms' ),
27 | editText: createInterpolateElement(
28 | __(
29 | 'Edit your quizzes on crowdsignal.com ',
30 | 'crowdsignal-forms'
31 | ),
32 | {
33 | a: (
34 | // eslint-disable-next-line jsx-a11y/anchor-has-content
35 |
40 | ),
41 | }
42 | ),
43 | dashboardLink: 'https://app.crowdsignal.com/?ref=quizmbedblock',
44 | embedMessage: __(
45 | 'Paste a link to the quiz you want to display on your site',
46 | 'crowdsignal-forms'
47 | ),
48 | },
49 | keywords: [
50 | __( 'quiz', 'crowdsignal-forms' ),
51 | __( 'quizzes', 'crowdsignal-forms' ),
52 | ],
53 | isActive: ( blockAttributes, variationAttributes ) =>
54 | blockAttributes.typeText === variationAttributes.typeText,
55 | },
56 | ];
57 |
--------------------------------------------------------------------------------
/client/blocks/feedback/constants.js:
--------------------------------------------------------------------------------
1 | export const views = {
2 | QUESTION: 'question',
3 | SUBMIT: 'submit',
4 | };
5 |
6 | export const FeedbackStatus = Object.freeze( {
7 | OPEN: 'open',
8 | CLOSED: 'closed',
9 | CLOSED_AFTER: 'closed-after',
10 | } );
11 |
12 | export const FeedbackToggleMode = Object.freeze( {
13 | CLICK: 'click',
14 | HOVER: 'hover',
15 | PAGE_LOAD: 'load',
16 | } );
17 |
--------------------------------------------------------------------------------
/client/blocks/feedback/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import FeedbackIcon from 'components/icon/feedback';
10 | import attributes from './attributes';
11 | import EditFeedbackBlock from './edit';
12 |
13 | export default {
14 | title: __( 'Feedback Button', 'crowdsignal-forms' ),
15 | description: __(
16 | 'Add an always visible button that allows your audience to share feedback anytime.',
17 | 'crowdsignal-forms'
18 | ),
19 | category: 'crowdsignal-forms',
20 | keywords: [
21 | 'crowdsignal',
22 | __( 'feedback', 'crowdsignal-forms' ),
23 | __( 'floating', 'crowdsignal-forms' ),
24 | __( 'contact', 'crowdsignal-forms' ),
25 | __( 'call to action', 'crowdsignal-forms' ),
26 | __( 'cta', 'crowdsignal-forms' ),
27 | __( 'button', 'crowdsignal-forms' ),
28 | __( 'subscribe', 'crowdsignal-forms' ),
29 | __( 'form', 'crowdsignal-forms' ),
30 | __( 'email', 'crowdsignal-forms' ),
31 | __( 'message', 'crowdsignal-forms' ),
32 | ],
33 | icon: ,
34 | edit: EditFeedbackBlock,
35 | supports: {
36 | multiple: false,
37 | html: false,
38 | reusable: false,
39 | },
40 | attributes,
41 | usesContext: [ 'postId', 'queryId' ],
42 | example: {
43 | attributes: {
44 | isExample: true,
45 | },
46 | },
47 | };
48 |
--------------------------------------------------------------------------------
/client/blocks/feedback/toolbar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import { BlockControls } from '@wordpress/block-editor';
10 | import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
11 | import { __ } from '@wordpress/i18n';
12 |
13 | /**
14 | * Internal dependencies
15 | */
16 | import BlockAlignmentControl, {
17 | GRID,
18 | } from 'components/block-alignment-control';
19 | import { views } from './constants';
20 |
21 | const FeedbackToolbar = ( {
22 | attributes,
23 | currentView,
24 | onViewChange,
25 | setAttributes,
26 | } ) => {
27 | const handleViewChange = ( view ) => () => onViewChange( view );
28 |
29 | const handleSetPosition = ( row, column ) =>
30 | setAttributes( {
31 | x: column,
32 | y: row,
33 | } );
34 |
35 | return (
36 |
37 |
38 |
44 | { __( 'Question', 'crowdsignal-forms' ) }
45 |
46 |
52 | { __( 'Submit', 'crowdsignal-forms' ) }
53 |
54 |
55 |
56 |
69 |
70 |
71 | );
72 | };
73 |
74 | export default FeedbackToolbar;
75 |
--------------------------------------------------------------------------------
/client/blocks/feedback/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { kebabCase, mapKeys } from 'lodash';
5 |
6 | export const getStyleVars = ( attributes, fallbackStyles ) =>
7 | mapKeys(
8 | {
9 | backgroundColor: attributes.backgroundColor || '#ffffff',
10 | buttonColor: attributes.buttonColor || fallbackStyles.accentColor,
11 | buttonTextColor:
12 | attributes.buttonTextColor || fallbackStyles.textColorInverted,
13 | textColor: attributes.textColor || fallbackStyles.textColor,
14 | textSize: fallbackStyles.textSize,
15 | triggerBackgroundColor:
16 | attributes.triggerBackgroundColor || fallbackStyles.accentColor,
17 | triggerTextColor:
18 | attributes.triggerTextColor || fallbackStyles.textColorInverted,
19 | },
20 | ( _, key ) => `--crowdsignal-forms-${ kebabCase( key ) }`
21 | );
22 |
23 | export const isWidgetEditor = () => !! window.wp.customizeWidgets;
24 |
--------------------------------------------------------------------------------
/client/blocks/nps/attributes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import { NpsStatus } from './constants';
10 | /**
11 | * Note: Any changes made to the attributes definition need to be duplicated in
12 | * Crowdsignal_Forms\Frontend\Blocks\Crowdsignal_Forms_Nps_Block::attributes()
13 | * inside includes/frontend/blocks/class-crowdsignal-forms-nps-block.php.
14 | */
15 |
16 | export default {
17 | backgroundColor: {
18 | type: 'string',
19 | },
20 | buttonColor: {
21 | type: 'string',
22 | },
23 | buttonTextColor: {
24 | type: 'string',
25 | },
26 | feedbackPlaceholder: {
27 | type: 'string',
28 | default: __(
29 | 'Please help us understand your rating',
30 | 'crowdsignal-forms'
31 | ),
32 | },
33 | feedbackQuestion: {
34 | type: 'string',
35 | default: __(
36 | 'Thanks so much for your response! How could we do better?',
37 | 'crowdsignal-forms'
38 | ),
39 | },
40 | hideBranding: {
41 | type: 'boolean',
42 | default: false,
43 | },
44 | highRatingLabel: {
45 | type: 'string',
46 | default: __( 'Extremely likely', 'crowdsignal-forms' ),
47 | },
48 | lowRatingLabel: {
49 | type: 'string',
50 | default: __( 'Not likely at all', 'crowdsignal-forms' ),
51 | },
52 | ratingQuestion: {
53 | type: 'string',
54 | default: __(
55 | 'How likely is it that you would recommend this project to a friend or colleague?',
56 | 'crowdsignal-forms'
57 | ),
58 | },
59 | submitButtonLabel: {
60 | type: 'string',
61 | default: __( 'Submit', 'crowdsignal-forms' ),
62 | },
63 | surveyId: {
64 | type: 'number',
65 | default: null,
66 | },
67 | textColor: {
68 | type: 'string',
69 | },
70 | title: {
71 | type: 'string',
72 | default: '',
73 | },
74 | viewThreshold: {
75 | type: 'string',
76 | default: 2,
77 | },
78 | status: {
79 | type: 'string',
80 | default: NpsStatus.OPEN,
81 | },
82 | closedAfterDateTime: {
83 | type: 'string',
84 | default: null,
85 | },
86 | isExample: {
87 | type: 'boolean',
88 | default: false,
89 | },
90 | };
91 |
--------------------------------------------------------------------------------
/client/blocks/nps/constants.js:
--------------------------------------------------------------------------------
1 | export const views = {
2 | RATING: 'rating',
3 | FEEDBACK: 'feedback',
4 | SUBMIT: 'submit',
5 | };
6 |
7 | export const NpsStatus = Object.freeze( {
8 | OPEN: 'open',
9 | CLOSED: 'closed',
10 | CLOSED_AFTER: 'closed-after',
11 | } );
12 |
--------------------------------------------------------------------------------
/client/blocks/nps/edit.scss:
--------------------------------------------------------------------------------
1 | /* Editor styles */
2 |
3 | .crowdsignal-forms-nps.is-inactive {
4 | opacity: 0.6;
5 | }
6 |
7 | .crowdsignal-forms-nps__toolbar-toggle {
8 | font-weight: 600;
9 | padding-left: 16px !important;
10 | padding-right: 16px !important;
11 | }
12 |
13 | .crowdsignal-forms-nps__toolbar-popover {
14 | padding: 15px;
15 | min-width: 300px;
16 |
17 | .components-base-control__field {
18 | display: flex;
19 | flex-direction: row;
20 | align-items: center;
21 | margin-bottom: 0;
22 | }
23 |
24 | .components-base-control__label {
25 | display: block;
26 | flex-grow: 1;
27 | line-height: 30px;
28 | margin-bottom: 0;
29 | }
30 |
31 | .components-text-control__input {
32 | width: 5em;
33 | }
34 | }
35 |
36 | .crowdsignal-forms-nps__toolbar-popover-button {
37 |
38 | svg {
39 | margin-right: 0 !important;
40 | }
41 |
42 | &.components-button.has-icon .dashicon {
43 | margin-right: 2px;
44 | }
45 | }
46 |
47 | .crowdsignal-forms-nps__rating-button:hover {
48 | background-color: var(--crowdsignal-forms-button-text-color);
49 | border-color: var(--crowdsignal-forms-button-color);
50 | color: var(--crowdsignal-forms-button-color);
51 | }
52 |
53 | .editor-styles-wrapper .components-button.is-secondary.crowdsignal-forms-nps__preview-button {
54 | text-decoration: none;
55 |
56 | &.is-disabled {
57 | display: none;
58 | }
59 | }
60 |
61 | .editor-styles-wrapper .crowdsignal-forms-feedback__header {
62 | color: var(--crowdsignal-forms-text-color);
63 | }
64 |
--------------------------------------------------------------------------------
/client/blocks/nps/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import NpsIcon from 'components/icon/nps';
10 | import attributes from './attributes';
11 | import edit from './edit';
12 |
13 | export default {
14 | title: __( 'Measure NPS', 'crowdsignal-forms' ),
15 | description: __(
16 | 'Calculate your Net Promoter Score! Collect feedback and track customer satisfaction over time. — powered by Crowdsignal.',
17 | 'crowdsignal-forms'
18 | ),
19 | category: 'crowdsignal-forms',
20 | attributes,
21 | usesContext: [ 'postId', 'queryId' ],
22 | supports: {
23 | multiple: false,
24 | html: false,
25 | reusable: false,
26 | },
27 | icon: ,
28 | edit,
29 | keywords: [
30 | __( 'ask', 'crowdsignal-forms' ),
31 | 'crowdsignal',
32 | __( 'CSAT', 'crowdsignal-forms' ),
33 | __( 'customer experience', 'crowdsignal-forms' ),
34 | __( 'customer satisfaction', 'crowdsignal-forms' ),
35 | __( 'feedback', 'crowdsignal-forms' ),
36 | __( 'form', 'crowdsignal-forms' ),
37 | __( 'loyalty', 'crowdsignal-forms' ),
38 | __( 'net promoter score', 'crowdsignal-forms' ),
39 | __( 'nps', 'crowdsignal-forms' ),
40 | __( 'opinion', 'crowdsignal-forms' ),
41 | __( 'poll', 'crowdsignal-forms' ),
42 | __( 'promoter', 'crowdsignal-forms' ),
43 | __( 'research', 'crowdsignal-forms' ),
44 | __( 'rating', 'crowdsignal-forms' ),
45 | __( 'review', 'crowdsignal-forms' ),
46 | __( 'score', 'crowdsignal-forms' ),
47 | __( 'survey', 'crowdsignal-forms' ),
48 | ],
49 | example: {
50 | attributes: {
51 | isExample: true,
52 | ratingQuestion: __(
53 | 'How satisfied are you with the content of the site?',
54 | 'crowdsignal-forms'
55 | ),
56 | feedbackQuestion: __(
57 | 'Any advise on how we could improve your experience?',
58 | 'crowdsignal-forms'
59 | ),
60 | lowRatingLabel: __( 'Not satisfied', 'crowdsignal-forms' ),
61 | highRatingLabel: __( 'Very satisfied', 'crowdsignal-forms' ),
62 | },
63 | },
64 | };
65 |
--------------------------------------------------------------------------------
/client/blocks/nps/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { kebabCase, mapKeys } from 'lodash';
5 |
6 | export const getStyleVars = ( attributes, fallbackStyles ) =>
7 | mapKeys(
8 | {
9 | backgroundColor: attributes.backgroundColor || '#ffffff',
10 | buttonColor: attributes.buttonColor || fallbackStyles.accentColor,
11 | buttonTextColor:
12 | attributes.buttonTextColor || fallbackStyles.textColorInverted,
13 | textColor: attributes.textColor || fallbackStyles.textColor,
14 | textSize: fallbackStyles.textSize,
15 | },
16 | ( _, key ) => `--crowdsignal-forms-${ kebabCase( key ) }`
17 | );
18 |
--------------------------------------------------------------------------------
/client/blocks/poll/edit-bar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import { __ } from '@wordpress/i18n';
10 |
11 | const EditBar = ( { onEditClick } ) => {
12 | const handleEditClick = () => {
13 | onEditClick();
14 | };
15 | return (
16 |
17 |
18 | { __(
19 | 'Warning! This poll is published. Deleting or reordering answers may cause the loss of existing responses.',
20 | 'crowdsignal-forms'
21 | ) }
22 |
23 |
27 | { __( 'Edit', 'crowdsignal-forms' ) }
28 |
29 |
30 | );
31 | };
32 |
33 | export default EditBar;
34 |
--------------------------------------------------------------------------------
/client/blocks/poll/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import PollIcon from 'components/icon/poll';
10 | import 'state';
11 | import EditPollBlock from './edit';
12 | import attributes from './attributes';
13 |
14 | export default {
15 | title: __( 'Poll', 'crowdsignal-forms' ),
16 | description: __(
17 | 'Create polls and get your audience’s opinion — powered by Crowdsignal.',
18 | 'crowdsignal-forms'
19 | ),
20 | category: 'crowdsignal-forms',
21 | keywords: [
22 | __( 'ask', 'crowdsignal-forms' ),
23 | 'crowdsignal',
24 | __( 'feedback', 'crowdsignal-forms' ),
25 | __( 'form', 'crowdsignal-forms' ),
26 | __( 'opinion', 'crowdsignal-forms' ),
27 | __( 'poll', 'crowdsignal-forms' ),
28 | __( 'pop', 'crowdsignal-forms' ),
29 | __( 'question', 'crowdsignal-forms' ),
30 | __( 'quiz', 'crowdsignal-forms' ),
31 | __( 'research', 'crowdsignal-forms' ),
32 | __( 'survey', 'crowdsignal-forms' ),
33 | __( 'vote', 'crowdsignal-forms' ),
34 | ],
35 | icon: ,
36 | edit: EditPollBlock,
37 | attributes,
38 | usesContext: [ 'postId', 'queryId' ],
39 | supports: {
40 | align: [ 'center', 'wide', 'full' ],
41 | },
42 | getEditWrapperProps: ( { align } ) => ( {
43 | 'data-align': align,
44 | } ),
45 | example: {
46 | attributes: {
47 | question: __( 'How did you hear about us?', 'crowdsignal-forms' ),
48 | answers: [
49 | {
50 | text: __( 'Search', 'crowdsignal-forms' ),
51 | },
52 | {
53 | text: __( 'Friend', 'crowdsignal-forms' ),
54 | },
55 | {
56 | text: __( 'Email', 'crowdsignal-forms' ),
57 | },
58 | ],
59 | },
60 | },
61 | styles: [
62 | {
63 | name: 'default',
64 | label: __( 'List', 'crowdsignal-forms' ),
65 | isDefault: true,
66 | },
67 | {
68 | name: 'buttons',
69 | label: __( 'Buttons', 'crowdsignal-forms' ),
70 | },
71 | ],
72 | variations: [
73 | {
74 | isDefault: true,
75 | attributes: {
76 | // Force the correct className onto the block by default
77 | className: 'is-style-buttons',
78 | },
79 | },
80 | ],
81 | };
82 |
--------------------------------------------------------------------------------
/client/blocks/poll/toolbar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 | import { map } from 'lodash';
6 |
7 | /**
8 | * WordPress dependencies
9 | */
10 | import { BlockControls } from '@wordpress/block-editor';
11 | import { Toolbar } from '@wordpress/components';
12 | import { __ } from '@wordpress/i18n';
13 |
14 | /**
15 | * Internal dependencies
16 | */
17 | import ChecklistMultipleChoiceIcon from 'components/icon/checklist-multiple-choice';
18 | import ChecklistSingleChoiceIcon from 'components/icon/checklist-single-choice';
19 | import { toggleButtonStyleAvailability } from './util';
20 |
21 | const multipleChoiceControls = [
22 | {
23 | icon: ChecklistSingleChoiceIcon,
24 | title: __( 'Choose one answer', 'crowdsignal-forms' ),
25 | value: false,
26 | },
27 | {
28 | icon: ChecklistMultipleChoiceIcon,
29 | title: __( 'Choose multiple answers', 'crowdsignal-forms' ),
30 | value: true,
31 | },
32 | ];
33 |
34 | const PollToolbar = ( { attributes, setAttributes } ) => {
35 | const multipleChoiceToolbar = map( multipleChoiceControls, ( button ) => ( {
36 | ...button,
37 | isActive: button.value === attributes.isMultipleChoice,
38 | onClick: () => {
39 | setAttributes( { isMultipleChoice: button.value } );
40 |
41 | toggleButtonStyleAvailability( button.value );
42 | },
43 | } ) );
44 |
45 | return (
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default PollToolbar;
53 |
--------------------------------------------------------------------------------
/client/blocks/vote-item/attributes.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Note: Any changes made to the attributes definition need to be duplicated in
3 | * Crowdsignal_Forms\Frontend\Blocks\Crowdsignal_Forms_Vote_Item_Block::attributes()
4 | * inside includes/frontend/blocks/class-crowdsignal-forms-vote-item-block.php.
5 | */
6 | export default {
7 | answerId: {
8 | type: 'string',
9 | default: null,
10 | },
11 | type: {
12 | type: 'string',
13 | },
14 | textColor: {
15 | type: 'string',
16 | },
17 | backgroundColor: {
18 | type: 'string',
19 | },
20 | borderColor: {
21 | type: 'string',
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/client/blocks/vote-item/edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import { compose } from '@wordpress/compose';
10 |
11 | /**
12 | * Internal dependencies
13 | */
14 | import SideBar from './sidebar';
15 | import withClientId from 'components/with-client-id';
16 | import VoteItem from 'components/vote/vote-item';
17 | import { withFallbackStyles } from 'components/with-fallback-styles';
18 |
19 | const EditVoteItemBlock = ( props ) => {
20 | const { attributes, className, fallbackStyles, renderStyleProbe } = props;
21 |
22 | return (
23 | <>
24 |
25 |
26 |
34 |
35 | { renderStyleProbe() }
36 | >
37 | );
38 | };
39 |
40 | export default compose( [
41 | withFallbackStyles,
42 | withClientId( [ 'answerId' ] ),
43 | ] )( EditVoteItemBlock );
44 |
--------------------------------------------------------------------------------
/client/blocks/vote-item/edit.scss:
--------------------------------------------------------------------------------
1 | /* editor styles */
2 |
3 | [data-type="crowdsignal-forms/vote-item"] {
4 |
5 | /* Gutenberg 9 fix for vertically overridden margins in a horizontal
6 | orientation block. */
7 | margin-top: 28px !important;
8 | margin-bottom: 0 !important;
9 |
10 | &:not(:last-child) {
11 | margin-inline-end: 8px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/client/blocks/vote-item/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import VoteIcon from 'components/icon/vote';
10 | import EditVoteItemBlock from './edit';
11 | import attributes from './attributes';
12 |
13 | export default {
14 | title: __( 'Vote Item', 'crowdsignal-forms' ),
15 | description: __(
16 | 'Allow your audience to rate your work or express their opinion — powered by Crowdsignal.',
17 | 'crowdsignal-forms'
18 | ),
19 | category: 'crowdsignal-forms',
20 | parent: [ 'crowdsignal-forms/vote' ],
21 | icon: ,
22 | edit: EditVoteItemBlock,
23 | attributes,
24 | };
25 |
--------------------------------------------------------------------------------
/client/blocks/vote-item/sidebar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import { InspectorControls, PanelColorSettings } from '@wordpress/block-editor';
10 | import { __ } from '@wordpress/i18n';
11 |
12 | const SideBar = ( { attributes, setAttributes } ) => {
13 | const handleChangeTextColor = ( textColor ) =>
14 | setAttributes( { textColor } );
15 |
16 | const handleChangeBackgroundColor = ( backgroundColor ) =>
17 | setAttributes( { backgroundColor } );
18 |
19 | const handleChangeBorderColor = ( borderColor ) =>
20 | setAttributes( { borderColor } );
21 | return (
22 |
23 |
44 |
45 | );
46 | };
47 |
48 | export default SideBar;
49 |
--------------------------------------------------------------------------------
/client/blocks/vote/attributes.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Note: Any changes made to the attributes definition need to be duplicated in
3 | * Crowdsignal_Forms\Frontend\Blocks\Crowdsignal_Forms_Vote_Block::attributes()
4 | * inside includes/frontend/blocks/class-crowdsignal-forms-vote-block.php.
5 | */
6 |
7 | import { PollStatus } from './constants';
8 |
9 | export default {
10 | pollId: {
11 | type: 'string',
12 | default: null,
13 | },
14 | hideBranding: {
15 | type: 'boolean',
16 | default: false,
17 | },
18 | title: {
19 | type: 'string',
20 | default: null,
21 | },
22 | pollStatus: {
23 | type: 'string',
24 | default: PollStatus.OPEN,
25 | },
26 | closedAfterDateTime: {
27 | type: 'string',
28 | default: null,
29 | },
30 | size: {
31 | type: 'string',
32 | default: 'medium',
33 | },
34 | borderWidth: {
35 | type: 'number',
36 | default: 1,
37 | },
38 | borderRadius: {
39 | type: 'number',
40 | default: 5,
41 | },
42 | hideResults: {
43 | type: 'boolean',
44 | default: false,
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/client/blocks/vote/constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | export const PollStatus = Object.freeze( {
7 | OPEN: 'open',
8 | CLOSED: 'closed',
9 | CLOSED_AFTER: 'closed-after',
10 | } );
11 |
12 | export const ConnectedAccountState = Object.freeze( {
13 | CONNECTED: 'connected',
14 | NOT_CONNECTED: 'not-connected',
15 | NOT_VERIFIED: 'not-verified',
16 | } );
17 |
18 | export const DEFAULT_SIZE_CONTROLS = [
19 | {
20 | title: __( 'Small', 'crowdsignal-forms' ),
21 | size: 'small',
22 | },
23 | {
24 | title: __( 'Medium', 'crowdsignal-forms' ),
25 | size: 'medium',
26 | },
27 | {
28 | title: __( 'Large', 'crowdsignal-forms' ),
29 | size: 'large',
30 | },
31 | ];
32 |
33 | export const POPOVER_PROPS = {
34 | position: 'bottom right',
35 | isAlternate: true,
36 | className: 'crowdsignal-forms-vote__size-dropdown',
37 | };
38 |
--------------------------------------------------------------------------------
/client/blocks/vote/edit.scss:
--------------------------------------------------------------------------------
1 | /* editor styles */
2 |
3 | .crowdsignal-forms-vote .block-editor-block-list__layout {
4 | display: flex;
5 | flex-direction: row;
6 | }
7 |
8 | .crowdsignal-forms__border-popover .crowdsignal-forms__row {
9 | padding: 10px;
10 | }
11 |
12 | .crowdsignal-forms-vote-item__count {
13 |
14 | .crowdsignal-forms-vote.no-results & {
15 | display: none;
16 | }
17 | }
18 |
19 | .crowdsignal-forms-vote__size-dropdown {
20 |
21 | .components-button.components-dropdown-menu__menu-item.is-active::after {
22 | content: "\2713";
23 | margin-inline-start: auto;
24 | margin-inline-end: 0;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/client/blocks/vote/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { InnerBlocks } from '@wordpress/block-editor';
5 | import { __ } from '@wordpress/i18n';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import VoteIcon from 'components/icon/vote';
11 | import EditVoteBlock from './edit';
12 | import attributes from './attributes';
13 |
14 | export default {
15 | title: __( 'Vote', 'crowdsignal-forms' ),
16 | description: __(
17 | 'Allow your audience to rate your work or express their opinion — powered by Crowdsignal.',
18 | 'crowdsignal-forms'
19 | ),
20 | category: 'crowdsignal-forms',
21 | keywords: [
22 | __( 'ballot', 'crowdsignal-forms' ),
23 | __( 'button', 'crowdsignal-forms' ),
24 | __( 'count', 'crowdsignal-forms' ),
25 | 'crowdsignal',
26 | __( 'deciding', 'crowdsignal-forms' ),
27 | __( 'decision', 'crowdsignal-forms' ),
28 | __( 'elect', 'crowdsignal-forms' ),
29 | __( 'election', 'crowdsignal-forms' ),
30 | __( 'feedback', 'crowdsignal-forms' ),
31 | __( 'form', 'crowdsignal-forms' ),
32 | __( 'like', 'crowdsignal-forms' ),
33 | __( 'nero', 'crowdsignal-forms' ),
34 | __( 'opinion', 'crowdsignal-forms' ),
35 | __( 'poll', 'crowdsignal-forms' ),
36 | __( 'polling', 'crowdsignal-forms' ),
37 | __( 'rate', 'crowdsignal-forms' ),
38 | __( 'rating', 'crowdsignal-forms' ),
39 | __( 'research', 'crowdsignal-forms' ),
40 | __( 'survey', 'crowdsignal-forms' ),
41 | __( 'thumb down', 'crowdsignal-forms' ),
42 | __( 'thumb up', 'crowdsignal-forms' ),
43 | __( 'thumbs', 'crowdsignal-forms' ),
44 | __( 'vote', 'crowdsignal-forms' ),
45 | __( 'voting', 'crowdsignal-forms' ),
46 | ],
47 | icon: ,
48 | edit: EditVoteBlock,
49 | save: () => ,
50 | attributes,
51 | usesContext: [ 'postId', 'queryId' ],
52 | example: {
53 | attributes: {
54 | className: 'crowdsignal-forms-vote__example',
55 | size: 'large',
56 | },
57 | },
58 | };
59 |
--------------------------------------------------------------------------------
/client/blocks/vote/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import classNames from 'classnames';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import { isEmpty, kebabCase, mapKeys } from 'lodash';
10 |
11 | export const getVoteStyleVars = ( attributes ) => {
12 | return mapKeys(
13 | {
14 | borderRadius: `${ attributes.borderRadius }px`,
15 | borderWidth: `${ attributes.borderWidth }px`,
16 | },
17 | ( _, key ) => `--crowdsignal-forms-vote-${ kebabCase( key ) }`
18 | );
19 | };
20 |
21 | export const getVoteItemStyleVars = ( attributes, fallbackStyles ) => {
22 | const textColor = isEmpty( attributes.textColor )
23 | ? fallbackStyles.textColor
24 | : attributes.textColor;
25 | const backgroundColor = isEmpty( attributes.backgroundColor )
26 | ? fallbackStyles.backgroundColor
27 | : attributes.backgroundColor;
28 |
29 | return mapKeys(
30 | {
31 | borderColor: attributes.borderColor,
32 | bgColor: backgroundColor,
33 | textColor,
34 | votedColor: fallbackStyles.accentColor,
35 | },
36 | ( _, key ) => `--crowdsignal-forms-vote-${ kebabCase( key ) }`
37 | );
38 | };
39 |
40 | /**
41 | * Returns a css 'class' string of overridden styles given a collection of attributes.
42 | *
43 | * @param {*} attributes The block's attributes
44 | * @param {...any} extraClasses A list of additional classes to add to the class string
45 | */
46 | export const getBlockCssClasses = ( attributes, ...extraClasses ) => {
47 | return classNames(
48 | {
49 | 'has-bg-color': attributes.backgroundColor,
50 | 'has-text-color': attributes.textColor,
51 | 'has-border-color': attributes.borderColor,
52 | },
53 | extraClasses
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/client/components/block-alignment-control/constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | export const GRID = {
7 | '2x2': {
8 | rows: [
9 | {
10 | label: __( `Top`, 'crowdsignal-forms' ),
11 | value: 'top',
12 | },
13 | {
14 | label: __( `Bottom`, 'crowdsignal-forms' ),
15 | value: 'bottom',
16 | },
17 | ],
18 | columns: [
19 | {
20 | label: __( `Left`, 'crowdsignal-forms' ),
21 | value: 'left',
22 | },
23 | {
24 | label: __( `Right`, 'crowdsignal-forms' ),
25 | value: 'right',
26 | },
27 | ],
28 | },
29 | '2x3': {
30 | rows: [
31 | {
32 | label: __( `Top`, 'crowdsignal-forms' ),
33 | value: 'top',
34 | },
35 | {
36 | label: __( `Center`, 'crowdsignal-forms' ),
37 | value: 'center',
38 | },
39 | {
40 | label: __( `Bottom`, 'crowdsignal-forms' ),
41 | value: 'bottom',
42 | },
43 | ],
44 | columns: [
45 | {
46 | label: __( `Left`, 'crowdsignal-forms' ),
47 | value: 'left',
48 | },
49 | {
50 | label: __( `Right`, 'crowdsignal-forms' ),
51 | value: 'right',
52 | },
53 | ],
54 | },
55 | };
56 |
--------------------------------------------------------------------------------
/client/components/block-alignment-control/grid-button.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React, { useCallback } from 'react';
5 | import { CompositeItem } from 'reakit';
6 | import classnames from 'classnames';
7 |
8 | /**
9 | * WordPress dependencies
10 | */
11 | import { Tooltip, VisuallyHidden } from '@wordpress/components';
12 |
13 | const BlockAlignmentControlGridButton = ( {
14 | isActive,
15 | column,
16 | onSelect,
17 | row,
18 | ...props
19 | } ) => {
20 | const label = `${ row.label } ${ column.label }`;
21 |
22 | const handleSelect = useCallback( () => {
23 | onSelect( row.value, column.value );
24 | }, [ onSelect, row.value, column.value ] );
25 |
26 | const classes = classnames(
27 | 'crowdsignal-forms__block-alignment-control-button',
28 | {
29 | 'is-active': isActive,
30 | }
31 | );
32 |
33 | return (
34 |
35 |
41 | { label }
42 |
43 |
44 | );
45 | };
46 |
47 | export default BlockAlignmentControlGridButton;
48 |
--------------------------------------------------------------------------------
/client/components/block-alignment-control/grid.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React, { useEffect } from 'react';
5 | import { Composite, CompositeGroup, useCompositeState } from 'reakit';
6 | import { map } from 'lodash';
7 |
8 | /**
9 | * WordPress dependencies
10 | */
11 | import { useInstanceId } from '@wordpress/compose';
12 | import { isRTL } from '@wordpress/i18n';
13 |
14 | /**
15 | * Internal dependencies
16 | */
17 | import GridButton from './grid-button';
18 |
19 | const getButtonId = ( prefix, row, column ) =>
20 | `${ prefix }-${ row }-${ column }`;
21 |
22 | function BlockAlignmentControlGrid( { columns, onChange, rows, value } ) {
23 | const baseId = useInstanceId(
24 | BlockAlignmentControlGrid,
25 | 'block-alignment-control-grid'
26 | );
27 |
28 | const composite = useCompositeState( {
29 | baseId,
30 | currentId: getButtonId( baseId, value.row, value.column ),
31 | rtl: isRTL(),
32 | } );
33 |
34 | useEffect( () => {
35 | composite.setCurrentId(
36 | getButtonId( baseId, value.row, value.column )
37 | );
38 | }, [ value, composite.setCurrentId ] );
39 |
40 | return (
41 |
45 | { map( rows, ( row ) => (
46 |
52 | { map( columns, ( column ) => {
53 | const id = getButtonId(
54 | baseId,
55 | row.value,
56 | column.value
57 | );
58 | const isActive =
59 | composite.currentId ===
60 | getButtonId( baseId, row.value, column.value );
61 |
62 | return (
63 |
73 | );
74 | } ) }
75 |
76 | ) ) }
77 |
78 | );
79 | }
80 |
81 | export default BlockAlignmentControlGrid;
82 |
--------------------------------------------------------------------------------
/client/components/block-alignment-control/icon.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 | import classnames from 'classnames';
6 | import { map } from 'lodash';
7 |
8 | const BlockAlignmentControlIcon = ( { rows, columns, value } ) => {
9 | let spanKeyNum = 0;
10 | let divKeyNum = 0;
11 | return (
12 |
13 | { map( rows, ( row ) => (
14 |
18 | { map( columns, ( column ) => {
19 | const isActive =
20 | row.value === value.row &&
21 | column.value === value.column;
22 |
23 | const classes = classnames(
24 | 'crowdsignal-forms__block-alignment-control-icon-dot',
25 | {
26 | 'is-active': isActive,
27 | }
28 | );
29 |
30 | return (
31 |
32 | );
33 | } ) }
34 |
35 | ) ) }
36 |
37 | );
38 | };
39 |
40 | export default BlockAlignmentControlIcon;
41 |
--------------------------------------------------------------------------------
/client/components/block-alignment-control/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 | import { noop } from 'lodash';
6 |
7 | /**
8 | * WordPress dependencies
9 | */
10 | import { ToolbarButton, Dropdown, Tooltip } from '@wordpress/components';
11 | import { __ } from '@wordpress/i18n';
12 | import { DOWN } from '@wordpress/keycodes';
13 |
14 | /**
15 | * Internal dependencies
16 | */
17 | import Grid from './grid';
18 | import Icon from './icon';
19 |
20 | const BlockAlignmentControl = ( {
21 | closeOnSelectionChanged,
22 | disabled,
23 | label,
24 | onChange,
25 | rows,
26 | columns,
27 | value,
28 | } ) => {
29 | const toolbarIcon = (
30 |
31 | );
32 |
33 | return (
34 | {
40 | const openOnArrowDown = ( event ) => {
41 | if ( isOpen || event.keyCode !== DOWN ) {
42 | return;
43 | }
44 |
45 | event.preventDefault();
46 | event.stopPropagation();
47 | onToggle();
48 | };
49 |
50 | return (
51 |
52 |
61 |
62 | );
63 | } }
64 | renderContent={ ( { onClose } ) => {
65 | const handleChange = ( row, column ) => {
66 | onChange( row, column );
67 |
68 | if (
69 | closeOnSelectionChanged &&
70 | ( value.row !== row || value.column !== column )
71 | ) {
72 | onClose();
73 | }
74 | };
75 |
76 | return (
77 |
83 | );
84 | } }
85 | />
86 | );
87 | };
88 |
89 | BlockAlignmentControl.defaultProps = {
90 | closeOnSelectionChanged: false,
91 | label: __( 'Change block position', 'crowdsignal-forms' ),
92 | onChange: noop,
93 | };
94 |
95 | export default BlockAlignmentControl;
96 |
97 | export { GRID } from './constants';
98 |
--------------------------------------------------------------------------------
/client/components/block-alignment-control/style.scss:
--------------------------------------------------------------------------------
1 | .crowdsignal-forms__block-alignment-control-popover {
2 |
3 | .components-popover__content {
4 | min-width: auto !important;
5 | }
6 | }
7 |
8 | .crowdsignal-forms__block-alignment-control-grid {
9 | display: flex;
10 | flex-direction: column;
11 | }
12 |
13 | .crowdsignal-forms__block-alignment-control-row {
14 | display: flex;
15 | }
16 |
17 | .crowdsignal-forms__block-alignment-control-button {
18 | align-items: center;
19 | border: 0;
20 | background: transparent;
21 | cursor: pointer;
22 | display: flex;
23 | height: 30px;
24 | justify-content: center;
25 | width: 30px;
26 |
27 | &::before {
28 | background-color: #b5bcc2;
29 | display: block;
30 | content: "";
31 | height: 6px;
32 | width: 6px;
33 | }
34 |
35 | &:hover::before {
36 | background-color: #007cba;
37 | }
38 |
39 | &.is-active::before {
40 | background-color: #000;
41 | box-shadow: #000 0 0 0 2px;
42 | }
43 | }
44 |
45 | .crowdsignal-forms__block-alignment-control-icon {
46 | display: flex;
47 | flex-direction: column;
48 | height: 24px;
49 | justify-content: space-between;
50 | width: 24px;
51 | }
52 |
53 | .crowdsignal-forms__block-alignment-control-icon-row {
54 | display: flex;
55 | justify-content: space-between;
56 | width: 100%;
57 | }
58 |
59 | .crowdsignal-forms__block-alignment-control-icon-dot {
60 | display: flex;
61 | padding: 2px;
62 |
63 | &::before {
64 | background-color: #000;
65 | content: "";
66 | display: block;
67 | height: 2px;
68 | width: 2px;
69 | }
70 |
71 | &.is-active::before {
72 | box-shadow: #000 0 0 0 2px;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/client/components/brand-link/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 | import PropTypes from 'prop-types';
6 | import { __ } from '@wordpress/i18n';
7 |
8 | const BrandLink = ( { showBranding, referralCode } ) => {
9 | return (
10 |
25 | );
26 | };
27 |
28 | BrandLink.propTypes = {
29 | showBranding: PropTypes.bool,
30 | referralCode: PropTypes.string.isRequired,
31 | };
32 |
33 | export default BrandLink;
34 |
--------------------------------------------------------------------------------
/client/components/brand-link/style.scss:
--------------------------------------------------------------------------------
1 | .crowdsignal-forms__branding {
2 | display: flex;
3 | margin: 8px 4px 0;
4 | font-size: 8px;
5 |
6 | .crowdsignal-forms__branding-link {
7 | font-family: $font-sans-serif;
8 | text-decoration: none !important;
9 | text-transform: uppercase;
10 | box-shadow: none;
11 | border: 0;
12 |
13 | &:hover {
14 | box-shadow: none;
15 | }
16 |
17 | &.with-external-icon::after {
18 | content: "\2197";
19 | display: inline;
20 | font-size: 6px;
21 | vertical-align: top;
22 | }
23 |
24 | &:not(:hover) {
25 | color: var(--crowdsignal-forms-text-color);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/client/components/connect-to-crowdsignal/style.scss:
--------------------------------------------------------------------------------
1 | .crowdsignal-forms__connect-to-crowdsignal {
2 | border: 1px solid rgb(0, 0, 0);
3 | font-family: $font-sans-serif;
4 | padding: 24px;
5 | text-align: initial;
6 | }
7 |
8 | .crowdsignal-forms__connect-to-crowdsignal-header {
9 | display: flex;
10 | flex-direction: row;
11 | align-items: center;
12 | }
13 |
14 | .crowdsignal-forms__connect-to-crowdsignal-body {
15 | font-size: $font-size-gutenberg-system-default;
16 | margin-top: 24px;
17 | margin-bottom: 16px;
18 | }
19 |
20 | .crowdsignal-forms__connect-to-crowdsignal-title {
21 | font-size: 24pt;
22 | margin-inline-start: 16px;
23 | }
24 |
--------------------------------------------------------------------------------
/client/components/dialog-wrapper/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React, { useRef } from 'react';
5 |
6 | const DialogWrapper = ( { children, onClose } ) => {
7 | const wrapper = useRef( null );
8 |
9 | const handleClose = ( event ) =>
10 | event.target === wrapper.current && onClose();
11 |
12 | return (
13 | // eslint-disable-next-line
14 |
21 | { children }
22 |
23 | );
24 | };
25 |
26 | export default DialogWrapper;
27 |
--------------------------------------------------------------------------------
/client/components/dialog-wrapper/style.scss:
--------------------------------------------------------------------------------
1 | .crowdsignal-forms-dialog-wrapper {
2 | align-items: center;
3 | background: rgba(0, 0, 0, 0.3);
4 | display: flex;
5 | justify-content: center;
6 | position: fixed;
7 | bottom: 0;
8 | left: 0;
9 | right: 0;
10 | top: 0;
11 | z-index: 10000;
12 | }
13 |
--------------------------------------------------------------------------------
/client/components/editor-notice/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Notice, Icon } from '@wordpress/components';
5 |
6 | const EditorNotice = ( {
7 | icon,
8 | children,
9 | componentActions = [],
10 | ...props
11 | } ) => {
12 | return (
13 |
14 | { icon && (
15 |
16 | { }
17 |
18 | ) }
19 |
20 | { children }
21 |
22 | { componentActions.map( ( component ) => component ) }
23 |
24 | );
25 | };
26 |
27 | export default EditorNotice;
28 |
--------------------------------------------------------------------------------
/client/components/editor-notice/style.scss:
--------------------------------------------------------------------------------
1 | .crowdsignal-forms__editor-notice {
2 | margin: 0 0 15px !important;
3 |
4 | .components-notice__content {
5 | display: flex;
6 | flex-direction: row;
7 | align-items: center;
8 | }
9 | }
10 |
11 | .crowdsignal-forms__editor-notice-icon {
12 | line-height: 0;
13 | padding: 8px 16px 8px 8px;
14 |
15 | .is-warn & {
16 | color: var(--wp-admin-theme-color);
17 | }
18 | }
19 |
20 | .crowdsignal-forms__editor-notice-text {
21 | flex-grow: 1;
22 | color: var(--crowdsignal-forms-text-color);
23 |
24 | a {
25 | text-decoration: underline;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/client/components/feedback/popover.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React, { useRef, useState } from 'react';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import { __ } from '@wordpress/i18n';
10 |
11 | /**
12 | * Internal dependencies
13 | */
14 | import { views } from 'blocks/feedback/constants';
15 | import FeedbackForm from './form';
16 | import FeedbackSubmit from './submit';
17 | import FooterBranding from 'components/footer-branding';
18 |
19 | const FeedbackPopover = ( { attributes } ) => {
20 | const [ view, setView ] = useState( views.QUESTION );
21 | const [ height, setHeight ] = useState( 'auto' );
22 |
23 | const popover = useRef( null );
24 |
25 | const handleSubmit = () => {
26 | setHeight( popover.current.offsetHeight );
27 | setView( views.SUBMIT );
28 | };
29 |
30 | const styles = {
31 | height,
32 | };
33 |
34 | return (
35 |
40 | { view === views.QUESTION && (
41 |
45 | ) }
46 | { view === views.SUBMIT && (
47 |
48 | ) }
49 | { ! attributes.hideBranding && (
50 |
58 | ) }
59 |
60 | );
61 | };
62 |
63 | export default FeedbackPopover;
64 |
--------------------------------------------------------------------------------
/client/components/feedback/submit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * Wordpress dependencies
8 | */
9 | import { decodeEntities } from '@wordpress/html-entities';
10 |
11 | const FeedbackSubmit = ( { attributes } ) => (
12 |
13 | { decodeEntities( attributes.submitText ).split( ' ' ).join( '\n' ) }
14 |
15 | );
16 |
17 | export default FeedbackSubmit;
18 |
--------------------------------------------------------------------------------
/client/components/feedback/toggle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React, {
5 | forwardRef,
6 | useCallback,
7 | useEffect,
8 | useLayoutEffect,
9 | } from 'react';
10 | import classnames from 'classnames';
11 |
12 | /**
13 | * WordPress dependencies
14 | */
15 | import { decodeEntities } from '@wordpress/html-entities';
16 | import { __ } from '@wordpress/i18n';
17 |
18 | /**
19 | * Internal dependencies
20 | */
21 | import CloseIcon from 'components/icon/close-small';
22 | import { FeedbackToggleMode } from 'blocks/feedback/constants';
23 |
24 | const FeedbackToggle = (
25 | { attributes, className, isOpen, onClick, onToggle },
26 | ref
27 | ) => {
28 | useLayoutEffect( onToggle, [ isOpen ] );
29 |
30 | useEffect( () => {
31 | if ( isOpen || attributes.toggleOn !== FeedbackToggleMode.PAGE_LOAD ) {
32 | return;
33 | }
34 |
35 | onClick();
36 | }, [] );
37 |
38 | const handleHover = useCallback( () => {
39 | if ( isOpen || attributes.toggleOn !== FeedbackToggleMode.HOVER ) {
40 | return;
41 | }
42 |
43 | onClick();
44 | }, [ attributes.toggleOn, isOpen ] );
45 |
46 | const classes = classnames(
47 | 'crowdsignal-forms-feedback__trigger',
48 | 'wp-block-button__link',
49 | className,
50 | {
51 | 'is-active': isOpen,
52 | }
53 | );
54 |
55 | return (
56 |
57 | { ! isOpen && (
58 |
64 |
65 | { decodeEntities( attributes.triggerLabel ) }
66 |
67 |
68 | ) }
69 | { isOpen && (
70 |
71 |
72 | { __( 'Close', 'crowdsignal-forms' ) }
73 |
74 | ) }
75 |
76 | );
77 | };
78 |
79 | export default forwardRef( FeedbackToggle );
80 |
--------------------------------------------------------------------------------
/client/components/feedback/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { isObject } from 'lodash';
5 |
6 | const addFrameOffsets = ( offset, frame ) => ( {
7 | left: offset.left + frame.x + window.scrollX,
8 | right:
9 | offset.right +
10 | ( window.innerWidth > frame.left + frame.width
11 | ? window.innerWidth - frame.left - frame.width
12 | : 0 ),
13 | top: offset.top + frame.y + window.scrollY,
14 | bottom:
15 | offset.bottom +
16 | ( window.innerHeight > frame.top + frame.height
17 | ? window.innerHeight - frame.top - frame.height
18 | : 0 ),
19 | } );
20 |
21 | const getFeedbackButtonHorizontalPosition = ( align, width, offset ) => {
22 | return {
23 | left: align === 'left' ? offset.left : null,
24 | right: align === 'right' ? offset.right : null,
25 | };
26 | };
27 |
28 | const getFeedbackButtonVerticalPosition = ( verticalAlign, height, offset ) => {
29 | if ( verticalAlign === 'center' ) {
30 | return {
31 | top: ( window.innerHeight - height ) / 2,
32 | bottom: null,
33 | };
34 | }
35 |
36 | return {
37 | top: verticalAlign === 'top' ? offset.top : null,
38 | bottom: verticalAlign === 'bottom' ? offset.bottom : null,
39 | };
40 | };
41 |
42 | export const getFeedbackButtonPosition = (
43 | align,
44 | verticalAlign,
45 | width,
46 | height,
47 | padding,
48 | frameElement = null
49 | ) => {
50 | let offset = {
51 | left: isObject( padding ) ? padding.left : padding,
52 | right: isObject( padding ) ? padding.right : padding,
53 | top: isObject( padding ) ? padding.top : padding,
54 | bottom: isObject( padding ) ? padding.bottom : padding,
55 | };
56 |
57 | if ( frameElement ) {
58 | offset = addFrameOffsets(
59 | offset,
60 | frameElement.getBoundingClientRect()
61 | );
62 | }
63 |
64 | return {
65 | ...getFeedbackButtonHorizontalPosition( align, width, offset ),
66 | ...getFeedbackButtonVerticalPosition( verticalAlign, height, offset ),
67 | };
68 | };
69 |
--------------------------------------------------------------------------------
/client/components/footer-branding/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | const FooterBranding = ( {
7 | showLogo,
8 | children,
9 | message,
10 | trackRef = 'cs-forms-poll',
11 | } ) => (
12 |
42 | );
43 |
44 | export default FooterBranding;
45 |
--------------------------------------------------------------------------------
/client/components/footer-branding/style.scss:
--------------------------------------------------------------------------------
1 | .crowdsignal-forms__footer-branding {
2 | align-items: center;
3 | display: flex;
4 | margin-top: 16px;
5 | width: 100%;
6 |
7 | img.crowdsignal-forms__footer-branding-logo {
8 | height: 50px;
9 | margin-left: auto;
10 | margin-right: 0;
11 | width: 50px;
12 | }
13 |
14 | .crowdsignal-forms__branding-promote {
15 | display: inline-flex;
16 | font-family: $font-sans-serif;
17 | font-size: 10px;
18 | text-decoration: none !important;
19 | box-shadow: none;
20 | border: 0;
21 | background-color: #a4a4a4a4;
22 | color: #fff;
23 | cursor: pointer;
24 | padding-right: 8px;
25 | padding-left: 8px;
26 | margin-left: 16px;
27 | border-radius: 2px;
28 | padding-top: 2px;
29 | padding-bottom: 2px;
30 | vertical-align: middle;
31 | }
32 | }
33 |
34 | .crowdsignal-forms__footer-cs-link {
35 | display: inline-flex;
36 | font-family: $font-sans-serif;
37 | font-size: inherit;
38 | line-height: inherit;
39 | text-decoration: none;
40 | text-transform: uppercase;
41 | vertical-align: middle;
42 | border-bottom: 0 solid var(--crowdsignal-forms-text-color) !important;
43 |
44 | &:not(:hover) {
45 | color: var(--crowdsignal-forms-text-color);
46 | opacity: 0.4;
47 | }
48 |
49 | .has-default-thankyou & {
50 | // hardcoded as the background has been forced to white
51 | // to match the video background
52 | color: #333;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/client/components/icon/border.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
10 |
18 |
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/client/components/icon/check-circle.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
15 |
24 |
29 |
30 |
31 |
32 | );
33 |
--------------------------------------------------------------------------------
/client/components/icon/checklist-multiple-choice.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/client/components/icon/checklist-single-choice.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
8 |
9 |
10 |
11 |
16 |
17 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/client/components/icon/close-small.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/client/components/icon/close.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
13 |
22 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 |
--------------------------------------------------------------------------------
/client/components/icon/counter.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/client/components/icon/feedback.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | export default () => (
7 |
13 |
18 |
19 |
20 |
21 |
22 | );
23 |
--------------------------------------------------------------------------------
/client/components/icon/nps.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/client/components/icon/pencil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { SVG, Path } from '@wordpress/primitives';
5 |
6 | const pencil = (
7 |
8 |
9 |
10 | );
11 |
12 | export default pencil;
13 |
--------------------------------------------------------------------------------
/client/components/icon/placement.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | const PlacementIcon = () => (
7 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | export default PlacementIcon;
22 |
--------------------------------------------------------------------------------
/client/components/icon/poll.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
8 |
13 |
20 |
27 |
34 |
35 | );
36 |
--------------------------------------------------------------------------------
/client/components/icon/quiz.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function QuizIcon() {
4 | return (
5 |
12 |
16 |
22 |
26 |
27 | );
28 | }
29 |
30 | export default QuizIcon;
31 |
--------------------------------------------------------------------------------
/client/components/icon/size.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/client/components/icon/survey.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Survey() {
4 | return (
5 |
12 |
18 |
24 |
28 |
29 | );
30 | }
31 |
32 | export default Survey;
33 |
--------------------------------------------------------------------------------
/client/components/icon/thumbs-down.js:
--------------------------------------------------------------------------------
1 | export default ( { className, fillColor = 'black' } ) => (
2 |
10 |
11 |
20 |
26 |
27 |
28 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 |
--------------------------------------------------------------------------------
/client/components/icon/thumbs-up.js:
--------------------------------------------------------------------------------
1 | export default ( { className, fillColor = 'black' } ) => (
2 |
10 |
11 |
20 |
26 |
27 |
28 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 |
--------------------------------------------------------------------------------
/client/components/icon/vote.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
18 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/client/components/icon/warning-circle.js:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
9 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/client/components/nps/feedback.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React, { useState } from 'react';
5 | import { isEmpty } from 'lodash';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import { updateNpsResponse } from 'data/nps';
11 |
12 | const NpsFeedback = ( { attributes, onSubmit, responseMeta } ) => {
13 | const [ feedback, setFeedback ] = useState( '' );
14 |
15 | const handleSubmit = async () => {
16 | if ( responseMeta !== null && ! isEmpty( feedback ) ) {
17 | updateNpsResponse( attributes.surveyId, {
18 | nonce: attributes.nonce,
19 | feedback,
20 | ...responseMeta,
21 | } );
22 | }
23 |
24 | onSubmit();
25 | };
26 |
27 | return (
28 |
29 |
36 |
37 |
38 |
43 | { attributes.submitButtonLabel }
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default NpsFeedback;
51 |
--------------------------------------------------------------------------------
/client/components/nps/rating.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React, { useState } from 'react';
5 | import classnames from 'classnames';
6 | import { pick, times } from 'lodash';
7 |
8 | /**
9 | * Internal dependencies
10 | */
11 | import { updateNpsResponse } from 'data/nps';
12 |
13 | const NpsRating = ( { attributes, onSubmit, onSubmitSuccess } ) => {
14 | const [ selected, setSelected ] = useState( -1 );
15 |
16 | const handleSubmit = ( rating ) => async () => {
17 | setSelected( rating );
18 |
19 | updateNpsResponse( attributes.surveyId, {
20 | nonce: attributes.nonce,
21 | score: rating,
22 | } ).then( ( data ) =>
23 | onSubmitSuccess( pick( data, [ 'r', 'checksum' ] ) )
24 | );
25 |
26 | // Wait for the animation to complete before proceeding to the next step
27 | setTimeout( onSubmit, 300 );
28 | };
29 |
30 | return (
31 |
32 |
33 | { attributes.lowRatingLabel }
34 | { attributes.highRatingLabel }
35 |
36 |
37 |
38 | { times( 11, ( n ) => {
39 | const classes = classnames(
40 | 'crowdsignal-forms-nps__rating-button',
41 | {
42 | 'is-active': n === selected,
43 | }
44 | );
45 |
46 | return (
47 |
53 | { n }
54 |
55 | );
56 | } ) }
57 |
58 |
59 | );
60 | };
61 |
62 | export default NpsRating;
63 |
--------------------------------------------------------------------------------
/client/components/poll/answer-results.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 | import PropTypes from 'prop-types';
6 | import classnames from 'classnames';
7 |
8 | /**
9 | * WordPress dependencies
10 | */
11 | import { decodeEntities } from '@wordpress/html-entities';
12 | import { _n, sprintf } from '@wordpress/i18n';
13 |
14 | const PollAnswerResults = ( { error, loading, text, totalVotes, votes } ) => {
15 | const classes = classnames( 'crowdsignal-forms-poll__answer-results', {
16 | 'is-error': error,
17 | 'is-loading': loading,
18 | } );
19 |
20 | const showResults = ! loading && ! error;
21 |
22 | const answerShare = 0 === totalVotes ? 0 : ( votes * 100 ) / totalVotes;
23 |
24 | const progressBarStyles = {
25 | width: `${ parseInt( answerShare, 10 ) }%`,
26 | };
27 |
28 | return (
29 |
30 |
31 |
32 | { decodeEntities( text ) }
33 |
34 |
35 |
36 | { showResults &&
37 | sprintf(
38 | /* translators: %s: Number of votes. */
39 | _n(
40 | '%s vote',
41 | '%s votes',
42 | votes,
43 | 'crowdsignal-forms'
44 | ),
45 | votes.toLocaleString()
46 | ) }
47 |
48 |
49 |
50 | { showResults && `${ answerShare.toFixed( 2 ) }%` }
51 |
52 |
53 |
54 |
60 |
61 | );
62 | };
63 |
64 | PollAnswerResults.propTypes = {
65 | loading: PropTypes.bool,
66 | text: PropTypes.string.isRequired,
67 | totalVotes: PropTypes.number,
68 | votes: PropTypes.number,
69 | };
70 |
71 | export default PollAnswerResults;
72 |
--------------------------------------------------------------------------------
/client/components/poll/closed-banner.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 | import classNames from 'classnames';
6 | import { __ } from '@wordpress/i18n';
7 |
8 | const ClosedBanner = ( {
9 | hasVoted,
10 | isPollClosed,
11 | isPollHidden,
12 | showSubmitMessage,
13 | } ) => {
14 | const classes = classNames(
15 | {
16 | 'is-transparent': showSubmitMessage,
17 | },
18 | 'crowdsignal-forms-poll__closed-banner'
19 | );
20 |
21 | let message = '';
22 | if ( isPollHidden ) {
23 | message = __( 'This Poll is Hidden', 'crowdsignal-forms' );
24 | } else if ( isPollClosed ) {
25 | message = __( 'This Poll is Closed', 'crowdsignal-forms' );
26 | } else if ( hasVoted ) {
27 | message = __( 'Thanks For Voting!', 'crowdsignal-forms' );
28 | }
29 |
30 | return (
31 |
32 | { message }
33 |
34 | );
35 | };
36 |
37 | export default ClosedBanner;
38 |
--------------------------------------------------------------------------------
/client/components/poll/error-banner.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | const ErrorBanner = ( { children } ) => (
7 | { children }
8 | );
9 |
10 | export default ErrorBanner;
11 |
--------------------------------------------------------------------------------
/client/components/poll/util.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import seedrandom from 'seedrandom';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import { shuffleWithGenerator, isAnswerEmpty } from './util';
10 |
11 | test( 'shuffleWithGenerator does not modify the original array', () => {
12 | const elements = [ 1, 2, 3, 4, 5 ];
13 | const elementsClone = elements.slice();
14 |
15 | shuffleWithGenerator( elements, new seedrandom() );
16 |
17 | expect( elements ).toEqual( elementsClone );
18 | } );
19 |
20 | test( 'shuffleWithGenerator does not gain or lose any elements', () => {
21 | const elements = [ 1, 2, 3, 4, 5 ];
22 | const shuffled = shuffleWithGenerator( elements, new seedrandom() );
23 |
24 | expect( shuffled ).toHaveLength( elements.length );
25 | expect( shuffled ).toEqual( expect.arrayContaining( elements ) );
26 | } );
27 |
28 | test( 'shuffleWithGenerator shuffles the same way twice when provided with the same random number generator', () => {
29 | const seed = 1;
30 | let rng = new seedrandom( seed );
31 | const elements = [ 1, 2, 3, 4, 5 ];
32 | const shuffled = shuffleWithGenerator( elements, rng );
33 |
34 | rng = new seedrandom( seed );
35 | const shuffledAgain = shuffleWithGenerator( elements, rng );
36 |
37 | expect( shuffled ).toEqual( shuffledAgain );
38 | } );
39 |
40 | test( 'shuffleWithGenerator actually shuffles the elements, when given a random number generator', () => {
41 | const rng = new seedrandom();
42 | const elements = [ 1, 2, 3, 4, 5 ];
43 | const shuffled = shuffleWithGenerator( elements, rng );
44 |
45 | expect( shuffled ).not.toEqual( elements );
46 | } );
47 |
48 | test( 'shuffleWithGenerator does not shuffle any elements, when given a static number generator', () => {
49 | const elements = [ 1, 2, 3, 4, 5 ];
50 |
51 | const shuffled = shuffleWithGenerator( elements, () => 1 );
52 |
53 | expect( elements ).toEqual( shuffled );
54 | } );
55 |
56 | test.each( [
57 | [ 'object is empty', {} ],
58 | [ 'object only has answerId', { answerId: 123 } ],
59 | [ 'object has only text as empty string', { text: '' } ],
60 | [ 'object has only empty text and answerId', { text: '', answerId: 123 } ],
61 | ] )( 'isAnswerEmpty returns true if %s', ( _, answer ) => {
62 | expect( isAnswerEmpty( answer ) ).toEqual( true );
63 | } );
64 |
65 | test.each( [
66 | [ 'object has non-empty text value', { text: 'answer value' } ],
67 | ] )( 'isAnswerEmpty returns false if %s', ( _, answer ) => {
68 | expect( isAnswerEmpty( answer ) ).toEqual( false );
69 | } );
70 |
--------------------------------------------------------------------------------
/client/components/promotional-tooltip/index.js:
--------------------------------------------------------------------------------
1 | /** WordPress dependencies */
2 | import { Tooltip } from '@wordpress/components';
3 | import { __ } from '@wordpress/i18n';
4 |
5 | const PromotionalTooltip = () => {
6 | return (
7 |
10 | { __( 'Hide Crowdsignal ads', 'crowdsignal-forms' ) }
11 |
12 | { __( 'and get unlimited', 'crowdsignal-forms' ) }
13 |
14 | { __( 'signals', 'crowdsignal-forms' ) } -{ ' ' }
15 |
20 | { __( 'Upgrade', 'crowdsignal-forms' ) }
21 |
22 |
23 | }
24 | position="top center"
25 | >
26 |
32 | { __(
33 | 'Hide',
34 | 'crowdsignal-forms'
35 | ) }
36 |
37 |
38 | );
39 | }
40 |
41 | export default PromotionalTooltip;
42 |
--------------------------------------------------------------------------------
/client/components/retry-notice/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import EditorNotice from 'components/editor-notice';
10 |
11 | const RetryNotice = ( { retryHandler } ) => {
12 | return (
13 |
25 | { __(
26 | `Unfortunately, the block couldn't be saved to Crowdsignal.com.`,
27 | 'crowdsignal-forms'
28 | ) }
29 |
30 | );
31 | };
32 |
33 | export default RetryNotice;
34 |
--------------------------------------------------------------------------------
/client/components/sidebar-promote/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 | import { Button, ExternalLink } from '@wordpress/components';
6 |
7 | const SidebarPromote = ( { signalWarning } ) => {
8 | return (
9 |
10 |
15 | { __( 'Upgrade', 'crowdsignal-forms' ) }
16 |
17 | { signalWarning ? (
18 |
19 |
20 | { __(
21 | 'Your free Crowdsignal account has ',
22 | 'crowdsignal-forms'
23 | ) }
24 |
25 |
26 | { __(
27 | 'reached the signals limit.',
28 | 'crowdsignal-forms'
29 | ) }
30 |
31 |
32 |
33 |
34 | ) : (
35 |
36 |
37 | { __(
38 | 'Hide Crowdsignal branding and get ',
39 | 'crowdsignal-forms'
40 | ) }
41 |
42 | { __( 'unlimited signals', 'crowdsignal-forms' ) }
43 |
44 |
45 |
46 | ) }
47 |
48 | );
49 | };
50 |
51 | export default SidebarPromote;
52 |
--------------------------------------------------------------------------------
/client/components/sidebar-promote/style.scss:
--------------------------------------------------------------------------------
1 | .crowdsignal-forms__sidebar-promote {
2 | margin-left: 16px;
3 | flex-grow: 1;
4 | }
5 |
--------------------------------------------------------------------------------
/client/components/signal-warning/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { ExternalLink } from '@wordpress/components';
5 | import { __ } from '@wordpress/i18n';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import EditorNotice from 'components/editor-notice';
11 |
12 | const SignalWarning = () => {
13 | return (
14 |
27 | { __( 'Your free Crowdsignal account has ', 'crowdsignal-forms' ) }
28 |
29 | { __( 'exceeded 2500 signals.', 'crowdsignal-forms' ) }
30 |
31 |
32 | );
33 | };
34 |
35 | export default SignalWarning;
36 |
--------------------------------------------------------------------------------
/client/components/use-autosave/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { useCallback, useEffect, useRef, useState } from 'react';
5 | import { debounce, values } from 'lodash';
6 |
7 | const SAVE_DEBOUNCE = 1500;
8 | const RETRY_THRESHOLD = 3;
9 |
10 | export const useAutosave = ( onSave, data = {} ) => {
11 | const [ error, setError ] = useState( false );
12 |
13 | const revision = useRef( 0 );
14 |
15 | const debouncedSave = useCallback(
16 | debounce(
17 | ( args, onFailure ) => onSave( args ).catch( onFailure ),
18 | SAVE_DEBOUNCE
19 | ),
20 | []
21 | );
22 |
23 | const handleSave = useCallback( ( savedRevision, retryCount = 1 ) => {
24 | setError( false );
25 |
26 | debouncedSave( data, () => {
27 | // Don't retry if there are new changes waiting to be saved
28 | if ( savedRevision !== revision.current ) {
29 | return;
30 | }
31 |
32 | if ( retryCount < RETRY_THRESHOLD ) {
33 | handleSave( savedRevision, retryCount + 1 );
34 | return;
35 | }
36 |
37 | setError( true );
38 | } );
39 | }, values( data ) );
40 |
41 | useEffect( () => {
42 | // Don't autosave on initial render
43 | if ( 0 === revision.current++ ) {
44 | return;
45 | }
46 |
47 | handleSave( revision.current );
48 | }, values( data ) );
49 |
50 | return {
51 | error,
52 | save: () => handleSave( revision.current ),
53 | };
54 | };
55 |
--------------------------------------------------------------------------------
/client/components/use-numbered-title/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { useEffect } from 'react';
5 | import { isEmpty, isNil } from 'lodash';
6 |
7 | const useNumberedTitle = (
8 | blockName,
9 | titlePrefix,
10 | attributes,
11 | setAttributes
12 | ) =>
13 | useEffect( () => {
14 | if ( isEmpty( window.csBlockTypeCount ) ) {
15 | window.csBlockTypeCount = {};
16 | }
17 |
18 | if ( isNil( window.csBlockTypeCount[ blockName ] ) ) {
19 | window.csBlockTypeCount[ blockName ] = 0;
20 | }
21 |
22 | window.csBlockTypeCount[ blockName ]++;
23 |
24 | if ( null !== attributes.title ) {
25 | // exit if title is set, but only after block count has been set, so newer blocks get the correct count.
26 | return;
27 | }
28 |
29 | if ( 1 === window.csBlockTypeCount[ blockName ] ) {
30 | setAttributes( {
31 | title: titlePrefix,
32 | } );
33 | } else {
34 | setAttributes( {
35 | title: `${ titlePrefix } ${ window.csBlockTypeCount[ blockName ] }`,
36 | } );
37 | }
38 | }, [] );
39 |
40 | export default useNumberedTitle;
41 |
--------------------------------------------------------------------------------
/client/components/use-poll-duplicate-cleaner/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { useEffect } from 'react';
5 | import { isEmpty, map, omit } from 'lodash';
6 |
7 | export default ( blockClientId, pollId, answers, setAttributes ) =>
8 | useEffect( () => {
9 | if ( isEmpty( pollId ) ) {
10 | return;
11 | }
12 |
13 | if ( ! window.csPolls ) {
14 | window.csPolls = {};
15 | }
16 |
17 | if ( ! window.csPolls[ pollId ] ) {
18 | window.csPolls[ pollId ] = [ blockClientId ];
19 | } else if ( window.csPolls[ pollId ].indexOf( blockClientId ) > -1 ) {
20 | // clientid already known, ignore.
21 | } else {
22 | const newAnswers = map( answers, ( answer ) =>
23 | omit( answer, [ 'answerId' ] )
24 | );
25 |
26 | setAttributes( { pollId: null, answers: newAnswers } );
27 | }
28 | }, [ pollId ] );
29 |
--------------------------------------------------------------------------------
/client/components/vote/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { round } from 'lodash';
5 |
6 | /**
7 | * Formats the counter values on vote items:
8 | *
9 | * @param {number} count Vote count
10 | * @return {string} Formatted count
11 | */
12 | export const formatVoteCount = ( count ) => {
13 | if ( ! count ) {
14 | return '0';
15 | }
16 |
17 | if ( count >= 10000000 ) {
18 | return `${ round( count / 1000000 ) }M`;
19 | }
20 |
21 | if ( count >= 1000000 ) {
22 | return `${ ( count / 1000000 ).toFixed( 1 ) }M`;
23 | }
24 |
25 | if ( count >= 10000 ) {
26 | return `${ round( count / 1000 ) }K`;
27 | }
28 |
29 | if ( count >= 1000 ) {
30 | return `${ ( count / 1000 ).toFixed( 1 ) }K`;
31 | }
32 |
33 | return count.toString();
34 | };
35 |
--------------------------------------------------------------------------------
/client/components/with-client-id/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React, { useEffect } from 'react';
5 | import { v4 as uuid } from 'uuid';
6 | import { forEach } from 'lodash';
7 |
8 | const withClientId = ( clientIdAttributes ) => ( Element ) => {
9 | return ( props ) => {
10 | const { attributes, setAttributes } = props;
11 |
12 | useEffect( () => {
13 | forEach( clientIdAttributes, ( key ) => {
14 | if ( attributes[ key ] ) {
15 | return;
16 | }
17 |
18 | setAttributes( {
19 | [ key ]: uuid(),
20 | } );
21 | } );
22 | }, [] );
23 |
24 | return ;
25 | };
26 | };
27 |
28 | export default withClientId;
29 |
--------------------------------------------------------------------------------
/client/components/with-fallback-styles/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import {
10 | getBackgroundColor,
11 | getBorderColor,
12 | withWordPressFallbackStyles,
13 | } from './util';
14 |
15 | const StyleProbe = () => (
16 |
17 |
18 |
Text
19 |
22 |
25 |
26 | );
27 |
28 | const getStyles = ( node ) => {
29 | if ( null === node ) {
30 | return {};
31 | }
32 |
33 | const buttonNode = node.querySelector( '.wp-block-button__link' );
34 | const textNode = node.querySelector( 'p' );
35 | const h3Node = node.querySelector( 'h3' );
36 | const wideContentNode = node.querySelector( '.alignwide' );
37 |
38 | let accentColor = getBackgroundColor( buttonNode );
39 | const backgroundColor = getBackgroundColor( textNode );
40 | const textColor = window.getComputedStyle( textNode ).color;
41 |
42 | // Ensure that we don't end up with the same color for surface and accent.
43 | // Falls back to button border color, then text color.
44 | if ( accentColor === backgroundColor ) {
45 | const borderColor = getBorderColor( buttonNode );
46 | accentColor = borderColor ? borderColor : textColor;
47 | }
48 |
49 | return {
50 | accentColor,
51 | backgroundColor,
52 | textColor,
53 | textColorInverted: window.getComputedStyle( buttonNode ).color,
54 | textFont: window.getComputedStyle( textNode ).fontFamily,
55 | textSize: window.getComputedStyle( textNode ).fontSize,
56 | headingFont: window.getComputedStyle( h3Node ).fontFamily,
57 | contentWideWidth: window.getComputedStyle( wideContentNode ).maxWidth,
58 | };
59 | };
60 |
61 | export const withFallbackStyles = ( WrappedComponent ) => {
62 | const getFallbackStyles = withWordPressFallbackStyles( ( node ) => ( {
63 | fallbackStyles: getStyles(
64 | node.querySelector( '.crowdsignal-forms__style-probe' )
65 | ),
66 | } ) );
67 |
68 | return getFallbackStyles( ( { fallbackStyles, ...props } ) => {
69 | const renderProbe = () => {
70 | if ( fallbackStyles ) {
71 | return null;
72 | }
73 |
74 | return ;
75 | };
76 |
77 | return (
78 |
83 | );
84 | } );
85 | };
86 |
--------------------------------------------------------------------------------
/client/components/with-fallback-styles/style.scss:
--------------------------------------------------------------------------------
1 | .crowdsignal-forms__style-probe {
2 | display: none;
3 | }
4 |
--------------------------------------------------------------------------------
/client/components/with-fallback-styles/util.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Internal dependencies
3 | */
4 | import { getBackgroundColor } from './util';
5 |
6 | test( 'getBackgroundColor returns parent color if passed in node has a transparent background', () => {
7 | const parentBackgroundColor = '#00ff00';
8 | const parentNode = document.createElement( 'div' );
9 | const node = document.createElement( 'div' );
10 | parentNode.appendChild( node );
11 |
12 | window.getComputedStyle = ( nodeToCheck ) => {
13 | let backgroundColor = 'rgba(0, 0, 0, 0)';
14 |
15 | if ( nodeToCheck === parentNode ) {
16 | backgroundColor = parentBackgroundColor;
17 | }
18 |
19 | return { backgroundColor };
20 | };
21 |
22 | expect( getBackgroundColor( node ) ).toEqual( parentBackgroundColor );
23 | } );
24 |
25 | test( 'getBackgroundColor returns current node background color if background is not transparent', () => {
26 | const backgroundColor = '#00ff00';
27 | const node = document.createElement( 'div' );
28 |
29 | window.getComputedStyle = () => ( { backgroundColor } );
30 |
31 | expect( getBackgroundColor( node ) ).toEqual( backgroundColor );
32 | } );
33 |
--------------------------------------------------------------------------------
/client/components/with-fse-check/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | import ErrorBanner from 'components/poll/error-banner';
7 | import { __ } from '@wordpress/i18n';
8 |
9 | const withFseCheck = ( Element ) => {
10 | return ( props ) => {
11 | const { context } = props;
12 | const { postId, queryId } = context;
13 |
14 | // Prevent block from loading in FSE or a query loop because save handlers don't support those contexts.
15 | // - double == instead of triple === used because we need to test for both null and undefined
16 | if ( null == postId ) {
17 | return { __( 'Crowdsignal blocks cannot be used outside of a post or page. The Site Editor is not supported.', 'crowdsignal-forms' ) } ;
18 | } else if ( null != queryId ) {
19 | return { __( 'Crowdsignal blocks are not supported inside a query loop.', 'crowdsignal-forms' ) } ;
20 | }
21 |
22 | return ;
23 | };
24 | };
25 |
26 | export default withFseCheck;
27 |
--------------------------------------------------------------------------------
/client/components/with-poll-base/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React, { useEffect } from 'react';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import { compose } from '@wordpress/compose';
10 |
11 | /**
12 | * Internal dependencies
13 | */
14 | import {
15 | startSubscriptions,
16 | startPolling,
17 | withPollDataSelect,
18 | withPollDataDispatch,
19 | } from 'blocks/poll/subscriptions';
20 | import usePollDuplicateCleaner from 'components/use-poll-duplicate-cleaner';
21 |
22 | startSubscriptions();
23 |
24 | const isP2tenberg = () => 'p2tenberg' in window || 'p2editor' in window;
25 |
26 | const withPollBase = ( Element ) => {
27 | return ( props ) => {
28 | const {
29 | attributes,
30 | setAttributes,
31 | addPollClientId,
32 | removePollClientId,
33 | } = props;
34 |
35 | useEffect( () => {
36 | if ( isP2tenberg() ) {
37 | startPolling();
38 | }
39 |
40 | if ( attributes.pollId ) {
41 | addPollClientId( attributes.pollId );
42 | }
43 |
44 | return () => {
45 | if ( attributes.pollId ) {
46 | removePollClientId( attributes.pollId );
47 | }
48 | };
49 | }, [] );
50 |
51 | usePollDuplicateCleaner(
52 | props.clientId,
53 | attributes.pollId,
54 | attributes.answers,
55 | setAttributes
56 | );
57 |
58 | return ;
59 | };
60 | };
61 |
62 | export default ( Element ) => {
63 | return compose( [
64 | withPollDataSelect(),
65 | withPollDataDispatch(),
66 | withPollBase,
67 | ] )( Element );
68 | };
69 |
--------------------------------------------------------------------------------
/client/cs-embed.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import CSEmbed from 'components/cs-embed';
10 | import MutationObserver from 'lib/mutation-observer';
11 |
12 | MutationObserver( 'data-crowdsignal-cs-embed', ( attributes ) => (
13 |
14 | ) );
15 |
--------------------------------------------------------------------------------
/client/data/feedback/edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import apiFetch from '@wordpress/api-fetch';
5 | import { trimEnd } from 'lodash';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import { withRequestTimeout } from 'data/util';
11 |
12 | export const updateFeedback = ( data ) =>
13 | withRequestTimeout(
14 | apiFetch( {
15 | path: trimEnd(
16 | `/crowdsignal-forms/v1/feedback/${ data.surveyId || '' }`,
17 | '/'
18 | ),
19 | method: 'POST',
20 | data,
21 | } )
22 | );
23 |
--------------------------------------------------------------------------------
/client/data/feedback/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { trimEnd } from 'lodash';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import apiFetch from '@crowdsignalForms/apifetch';
10 | import { withRequestTimeout } from 'data/util';
11 |
12 | export const updateFeedbackResponse = ( surveyId, data ) =>
13 | withRequestTimeout(
14 | apiFetch( {
15 | path: trimEnd(
16 | `/crowdsignal-forms/v1/feedback/${ surveyId || '' }/response`
17 | ),
18 | method: 'POST',
19 | data,
20 | } )
21 | );
22 |
--------------------------------------------------------------------------------
/client/data/hooks/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { useEffect, useState } from 'react';
5 | import Cookies from 'js-cookie';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import { requestResults, requestVoteNonce, requestVote } from 'data/poll';
11 | import { useFetch } from './util';
12 |
13 | export const usePollResults = ( pollId, doFetch = true ) => {
14 | const { data, error, loading } = useFetch(
15 | () => requestResults( pollId, doFetch ),
16 | [ pollId ]
17 | );
18 |
19 | return {
20 | error,
21 | loading,
22 | results: data,
23 | };
24 | };
25 |
26 | /**
27 | * React Hook that returns state variables for voting status and a function to perform a vote.
28 | *
29 | * @param {number} pollId ID of the poll being loaded.
30 | * @param {boolean} enableVoteTracking sets whether or not the vote cookie is read and set
31 | * @param {boolean} storeAnswerIdsInCookie sets whether or not the answer ids are stored in the vote restriction cookie
32 | */
33 | export const usePollVote = (
34 | pollId,
35 | enableVoteTracking = false,
36 | storeAnswerIdsInCookie = false
37 | ) => {
38 | const cookieName = `cs-poll-${ pollId }`;
39 | const [ isVoting, setIsVoting ] = useState( false );
40 | const [ hasVoted, setHasVoted ] = useState( false );
41 | const [ storedCookieValue, setStoredCookieValue ] = useState( '' );
42 |
43 | useEffect( () => {
44 | if ( enableVoteTracking && undefined !== Cookies.get( cookieName ) ) {
45 | setHasVoted( true );
46 | setStoredCookieValue( Cookies.get( cookieName ) );
47 | }
48 | }, [] );
49 |
50 | const vote = async ( selectedAnswerIds, voteCount = 1 ) => {
51 | try {
52 | setIsVoting( true );
53 | const nonce = await requestVoteNonce( pollId );
54 | await requestVote( nonce, pollId, selectedAnswerIds, voteCount );
55 |
56 | setHasVoted( true );
57 | if ( enableVoteTracking ) {
58 | const cookieValue = storeAnswerIdsInCookie
59 | ? selectedAnswerIds.join( ',' )
60 | : new Date().getTime();
61 |
62 | Cookies.set( cookieName, cookieValue, {
63 | sameSite: 'Strict',
64 | expires: 365,
65 | } );
66 |
67 | setStoredCookieValue( cookieValue );
68 | }
69 | } finally {
70 | setIsVoting( false );
71 | }
72 | };
73 |
74 | return {
75 | hasVoted,
76 | isVoting,
77 | vote,
78 | storedCookieValue,
79 | };
80 | };
81 |
--------------------------------------------------------------------------------
/client/data/hooks/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { useEffect, useState } from 'react';
5 |
6 | export const useFetch = ( fetchCallback, watchProps ) => {
7 | const [ data, setData ] = useState( null );
8 | const [ error, setError ] = useState( null );
9 | const [ loading, setLoading ] = useState( true );
10 |
11 | useEffect( () => {
12 | setLoading( true );
13 | setError( null );
14 | setData( null );
15 |
16 | fetchCallback()
17 | .then( setData )
18 | .catch( setError )
19 | .finally( () => setLoading( false ) );
20 | }, watchProps );
21 |
22 | return {
23 | data,
24 | error,
25 | loading,
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/client/data/nps/edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import apiFetch from '@wordpress/api-fetch';
5 | import { trimEnd } from 'lodash';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import { withRequestTimeout } from 'data/util';
11 |
12 | export const updateNps = ( data ) =>
13 | withRequestTimeout(
14 | apiFetch( {
15 | path: trimEnd(
16 | `/crowdsignal-forms/v1/nps/${ data.surveyId || '' }`,
17 | '/'
18 | ),
19 | method: 'POST',
20 | data,
21 | } )
22 | );
23 |
--------------------------------------------------------------------------------
/client/data/nps/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { trimEnd } from 'lodash';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import apiFetch from '@crowdsignalForms/apifetch';
10 | import { withRequestTimeout } from 'data/util';
11 |
12 | export const updateNpsResponse = ( surveyId, data ) =>
13 | withRequestTimeout(
14 | apiFetch( {
15 | path: trimEnd(
16 | `/crowdsignal-forms/v1/nps/${ surveyId || '' }/response`
17 | ),
18 | method: 'POST',
19 | data,
20 | } )
21 | );
22 |
--------------------------------------------------------------------------------
/client/data/util.js:
--------------------------------------------------------------------------------
1 | const WP_API_REQUEST_TIMEOUT = 10000;
2 |
3 | /**
4 | * Wraps a promise in a timeout that will reject
5 | * when it fails to complite within given time.
6 | *
7 | * @param {Promise} promise Promise
8 | * @return {Promise} Promise wrapped in a request timeout
9 | */
10 | export const withRequestTimeout = ( promise ) =>
11 | new Promise( ( resolve, reject ) => {
12 | const timer = setTimeout(
13 | () => reject( new Error( 'Request timed out' ) ),
14 | WP_API_REQUEST_TIMEOUT
15 | );
16 |
17 | promise.then( resolve, reject ).finally( () => clearTimeout( timer ) );
18 | } );
19 |
--------------------------------------------------------------------------------
/client/editor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { registerBlockType } from '@wordpress/blocks';
5 | import { addFilter } from '@wordpress/hooks';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import pollBlock from 'blocks/poll';
11 | import voteBlock from 'blocks/vote';
12 | import voteItemBlock from 'blocks/vote-item';
13 | import applauseBlock from 'blocks/applause';
14 | import npsBlock from 'blocks/nps';
15 | import feedbackBlock from 'blocks/feedback';
16 | import csEmbedBlock from 'blocks/cs-embed';
17 | import {
18 | withFixedPosition,
19 | withFixedPositionControl,
20 | } from 'components/with-fixed-position';
21 |
22 | registerBlockType( 'crowdsignal-forms/poll', pollBlock );
23 | registerBlockType( 'crowdsignal-forms/vote', voteBlock );
24 | registerBlockType( 'crowdsignal-forms/vote-item', voteItemBlock );
25 | registerBlockType( 'crowdsignal-forms/applause', applauseBlock );
26 | registerBlockType( 'crowdsignal-forms/nps', npsBlock );
27 | registerBlockType( 'crowdsignal-forms/feedback', feedbackBlock );
28 | registerBlockType( 'crowdsignal-forms/cs-embed', csEmbedBlock );
29 |
30 | addFilter(
31 | 'editor.BlockListBlock',
32 | 'crowdsignal-forms/with-fixed-position',
33 | withFixedPosition,
34 | 1
35 | );
36 | addFilter(
37 | 'editor.BlockEdit',
38 | 'crowdsignal-forms/with-fixed-position-control',
39 | withFixedPositionControl
40 | );
41 |
--------------------------------------------------------------------------------
/client/feedback.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import Feedback from 'components/feedback';
10 | import MutationObserver from 'lib/mutation-observer';
11 |
12 | MutationObserver( 'data-crowdsignal-feedback', ( attributes ) => (
13 |
14 | ) );
15 |
--------------------------------------------------------------------------------
/client/lib/mutation-observer/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { render } from 'react-dom';
5 | import { camelCase, isEmpty, forEach } from 'lodash';
6 |
7 | const MutationObserver = ( dataAttributeName, blockBuilder ) => {
8 | if ( 'complete' === document.readyState || 'interactive' === document.readyState ) {
9 | return blockObserver( dataAttributeName, blockBuilder );
10 | }
11 |
12 | document.addEventListener( 'DOMContentLoaded', () =>
13 | blockObserver( dataAttributeName, blockBuilder )
14 | );
15 | };
16 |
17 | const initBlocks = ( dataAttributeName, blockBuilder ) =>
18 | forEach(
19 | document.querySelectorAll( `div[${ dataAttributeName }]` ),
20 | ( element ) => {
21 | // Try-catch potentially prevents other blocks from breaking
22 | // when there's more then one on the page
23 | try {
24 | const attributes = JSON.parse(
25 | element.dataset[
26 | camelCase( dataAttributeName.substr( 'data-'.length ) )
27 | ]
28 | );
29 | const block = blockBuilder( attributes, element );
30 |
31 | element.removeAttribute( dataAttributeName );
32 |
33 | render( block, element );
34 | } catch ( error ) {
35 | // eslint-disable-next-line
36 | console.error(
37 | 'Crowdsignal Forms: Failed to parse block data for: %s',
38 | dataAttributeName
39 | );
40 | }
41 | }
42 | );
43 |
44 | const blockObserver = ( dataAttributeName, blockBuilder ) => {
45 | if (
46 | ! isEmpty( window.CrowdsignalMutationObservers ) &&
47 | true === window.CrowdsignalMutationObservers[ dataAttributeName ]
48 | ) {
49 | return;
50 | }
51 |
52 | const observer = new window.MutationObserver( () =>
53 | initBlocks( dataAttributeName, blockBuilder )
54 | );
55 |
56 | observer.observe( document.body, {
57 | attributes: true,
58 | attributeFilter: [ dataAttributeName ],
59 | childList: true,
60 | subtree: true,
61 | } );
62 |
63 | if ( isEmpty( window.CrowdsignalMutationObservers ) ) {
64 | window.CrowdsignalMutationObservers = [];
65 | }
66 |
67 | window.CrowdsignalMutationObservers[ dataAttributeName ] = true;
68 |
69 | // Run the first pass on load
70 | initBlocks( dataAttributeName, blockBuilder );
71 | };
72 |
73 | export default MutationObserver;
74 |
--------------------------------------------------------------------------------
/client/lib/tracks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { debounce } from 'lodash';
5 |
6 | export const trackFailedConnection = debounce( ( authorId, blockName ) => {
7 | window._tkq = window._tkq || [];
8 | window._tkq.push( [
9 | 'recordEvent',
10 | 'crowdsignal_connection_failed',
11 | {
12 | author_id: authorId,
13 | block_name: blockName,
14 | },
15 | ] );
16 | }, 5000 );
17 |
--------------------------------------------------------------------------------
/client/nps.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 | import { render } from 'react-dom';
6 | import { forEach } from 'lodash';
7 |
8 | /**
9 | * Internal dependencies
10 | */
11 | import DialogWrapper from 'components/dialog-wrapper';
12 | import NpsBlock from 'components/nps';
13 | import { NpsStatus } from 'blocks/nps/constants';
14 |
15 | const NPS_VIEWS_STORAGE_PREFIX = `cs-nps-views-`;
16 |
17 | window.addEventListener( 'load', () =>
18 | forEach(
19 | document.querySelectorAll( `div[data-crowdsignal-nps]` ),
20 | ( element ) => {
21 | try {
22 | const attributes = JSON.parse( element.dataset.crowdsignalNps );
23 | const viewThreshold = parseInt( attributes.viewThreshold, 10 );
24 |
25 | element.removeAttribute( 'data-crowdsignal-nps' );
26 |
27 | if ( NpsStatus.CLOSED === attributes.status ) {
28 | return;
29 | }
30 |
31 | if (
32 | NpsStatus.CLOSED_AFTER === attributes.status &&
33 | null !== attributes.closedAfterDateTime &&
34 | new Date().toISOString() > attributes.closedAfterDateTime
35 | ) {
36 | return;
37 | }
38 |
39 | if ( ! attributes.isPreview ) {
40 | const key = `${ NPS_VIEWS_STORAGE_PREFIX }${ attributes.surveyId }`;
41 | const viewCount =
42 | 1 +
43 | parseInt( window.localStorage.getItem( key ) || 0, 10 );
44 |
45 | window.localStorage.setItem( key, viewCount );
46 |
47 | if ( viewCount !== viewThreshold ) {
48 | return;
49 | }
50 | }
51 |
52 | const closeDialog = () => element.remove();
53 |
54 | render(
55 |
56 |
61 | ,
62 | element
63 | );
64 | } catch ( error ) {
65 | // eslint-disable-next-line no-console
66 | console.error( error );
67 | }
68 | }
69 | )
70 | );
71 |
--------------------------------------------------------------------------------
/client/poll.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import Poll from 'components/poll';
10 | import MutationObserver from 'lib/mutation-observer';
11 |
12 | MutationObserver( 'data-crowdsignal-poll', ( attributes ) => (
13 |
14 | ) );
15 |
--------------------------------------------------------------------------------
/client/state/account/actions.js:
--------------------------------------------------------------------------------
1 | import { ACCOUNT_INFO_LOAD, ACCOUNT_INFO_UPDATE } from '../action-types';
2 |
3 | export function loadAccountInfo() {
4 | return {
5 | type: ACCOUNT_INFO_LOAD,
6 | };
7 | }
8 |
9 | export function updateAccountInfo( data ) {
10 | return {
11 | type: ACCOUNT_INFO_UPDATE,
12 | data,
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/client/state/account/reducer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { combineReducers } from '@wordpress/data';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import { ACCOUNT_INFO_UPDATE } from '../action-types';
10 |
11 | const defaultAccountInfo = {
12 | is_verified: true,
13 | capabilities: [ 'hide-branding' ],
14 | signal_count: {
15 | count: 0,
16 | userLimit: 2500,
17 | shouldDisplay: false,
18 | },
19 | };
20 |
21 | const accountInfo = ( state = defaultAccountInfo, action ) => {
22 | if ( action.type === ACCOUNT_INFO_UPDATE ) {
23 | return {
24 | ...state,
25 | ...action.data,
26 | };
27 | }
28 |
29 | return state;
30 | };
31 |
32 | export default combineReducers( {
33 | accountInfo,
34 | } );
35 |
--------------------------------------------------------------------------------
/client/state/action-types.js:
--------------------------------------------------------------------------------
1 | export const ACCOUNT_INFO_LOAD = 'ACCOUNT_INFO_LOAD';
2 | export const ACCOUNT_INFO_UPDATE = 'ACCOUNT_INFO_UPDATE';
3 |
4 | // legacy
5 | export const SET_TRY_FETCH = 'SET_TRY_FETCH';
6 | export const IS_FETCHING = 'IS_FETCHING';
7 | export const SET_POLL = 'SET_POLL';
8 | export const ADD_POLL_CLIENT_ID = 'ADD_POLL_CLIENT_ID';
9 | export const REMOVE_POLL_CLIENT_ID = 'REMOVE_POLL_CLIENT_ID';
10 |
--------------------------------------------------------------------------------
/client/state/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_TRY_FETCH,
3 | IS_FETCHING,
4 | SET_POLL,
5 | ADD_POLL_CLIENT_ID,
6 | REMOVE_POLL_CLIENT_ID,
7 | } from './action-types';
8 |
9 | export function setTryFetchPollData( tryFetch ) {
10 | return {
11 | type: SET_TRY_FETCH,
12 | tryFetch,
13 | };
14 | }
15 |
16 | export function setIsFetchingPollData( isFetching ) {
17 | return {
18 | type: IS_FETCHING,
19 | isFetching,
20 | };
21 | }
22 |
23 | export function setPollApiDataForClientId( clientId, pollData ) {
24 | return {
25 | type: SET_POLL,
26 | clientId,
27 | pollData,
28 | };
29 | }
30 |
31 | export function addPollClientId( clientId ) {
32 | return {
33 | type: ADD_POLL_CLIENT_ID,
34 | clientId,
35 | };
36 | }
37 |
38 | export function removePollClientId( clientId ) {
39 | return {
40 | type: REMOVE_POLL_CLIENT_ID,
41 | clientId,
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/client/state/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * wordpress dependencies
3 | */
4 | import { createReduxStore, register } from '@wordpress/data';
5 |
6 | import * as mainActions from './actions';
7 | import * as accountActions from './account/actions';
8 | import reducer from './reducer';
9 | import { fetchAccountInfo } from '../data/poll';
10 |
11 | /**
12 | * Module Constants
13 | */
14 | export const STORE_NAME = 'crowdsignal-forms/editor';
15 |
16 | const storeConfig = {
17 | reducer,
18 |
19 | actions: {
20 | ...mainActions, // legacy, before store refactor
21 | ...accountActions,
22 | },
23 |
24 | selectors: {
25 | shouldTryFetchingPollData( state ) {
26 | return !! state?.tryFetch;
27 | },
28 | getPollDataByClientId( state, clientId ) {
29 | return state.pollsByClientId[ clientId ] || null;
30 | },
31 | getPollClientIds( state ) {
32 | return state.pollClientIds;
33 | },
34 | isFetchingPollData( state ) {
35 | return !! state?.isFetching;
36 | },
37 | getAccountInfo( state ) {
38 | return state.account.accountInfo;
39 | },
40 | },
41 |
42 | controls: {
43 | ACCOUNT_INFO_LOAD() {
44 | return fetchAccountInfo();
45 | },
46 | },
47 |
48 | resolvers: {
49 | *getAccountInfo() {
50 | const res = yield accountActions.loadAccountInfo();
51 | return accountActions.updateAccountInfo( res );
52 | },
53 | },
54 | };
55 |
56 | export const store = createReduxStore( STORE_NAME, storeConfig );
57 | register( store );
58 |
--------------------------------------------------------------------------------
/client/state/reducer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { combineReducers } from '@wordpress/data';
5 | import { filter } from 'lodash';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import account from './account/reducer';
11 | import {
12 | SET_TRY_FETCH,
13 | IS_FETCHING,
14 | SET_POLL,
15 | ADD_POLL_CLIENT_ID,
16 | REMOVE_POLL_CLIENT_ID,
17 | } from './action-types';
18 |
19 | const DEFAULT_STATE = {
20 | tryFetch: false,
21 | isFetching: false,
22 | pollsByClientId: {},
23 | pollClientIds: [],
24 | };
25 |
26 | const tryFetch = ( state = false, action ) => {
27 | if ( action.type === SET_TRY_FETCH ) {
28 | return !! action.tryFetch;
29 | }
30 | return state;
31 | };
32 | const isFetching = ( state = false, action ) => {
33 | if ( action.type === IS_FETCHING ) {
34 | return !! action.isFetching;
35 | }
36 | return state;
37 | };
38 | const pollsByClientId = ( state = {}, action ) => {
39 | if ( action.type === SET_POLL ) {
40 | return {
41 | ...state,
42 | [ action.clientId ]: action.pollData,
43 | };
44 | }
45 | return state;
46 | };
47 | const pollClientIds = ( state = [], action ) => {
48 | if ( action.type === ADD_POLL_CLIENT_ID ) {
49 | return [ ...state, action.clientId ];
50 | }
51 |
52 | if ( action.type === REMOVE_POLL_CLIENT_ID ) {
53 | return [
54 | ...filter( state, ( clientId ) => clientId !== action.clientId ),
55 | ];
56 | }
57 | return state;
58 | };
59 |
60 | export default combineReducers( {
61 | tryFetch,
62 | isFetching,
63 | pollsByClientId,
64 | pollClientIds,
65 | account,
66 | } );
67 |
--------------------------------------------------------------------------------
/client/vote.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import React from 'react';
5 | import { isEmpty, forEach } from 'lodash';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import Vote from 'components/vote';
11 | import MutationObserver from 'lib/mutation-observer';
12 |
13 | MutationObserver( 'data-crowdsignal-vote', ( attributes, element ) => {
14 | const innerBlocks = [];
15 |
16 | forEach( element.children, ( childElement ) => {
17 | if ( isEmpty( childElement.dataset.crowdsignalVoteItem ) ) {
18 | return;
19 | }
20 |
21 | innerBlocks.push(
22 | JSON.parse( childElement.dataset.crowdsignalVoteItem )
23 | );
24 | } );
25 |
26 | const voteAttributes = {
27 | ...attributes,
28 | innerBlocks,
29 | };
30 |
31 | return ;
32 | } );
33 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "automattic/crowdsignal-forms",
3 | "description": "Crowdsignal Forms",
4 | "type": "project",
5 | "license": "GPLv2",
6 | "require-dev": {
7 | "phpcompatibility/phpcompatibility-wp": "2.1.0",
8 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
9 | "wp-coding-standards/wpcs": "2.2.1",
10 | "psy/psysh": "^0.10.7",
11 | "wp-cli/wp-cli-bundle": "2.6"
12 | },
13 | "config": {
14 | "allow-plugins": {
15 | "cweagans/composer-patches": true,
16 | "dealerdirect/phpcodesniffer-composer-installer": true
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/crowdsignal-forms.php:
--------------------------------------------------------------------------------
1 | set_plugin_dir( $crowdsignal_forms_plugin_dir )
41 | ->register();
42 |
43 | Crowdsignal_Forms\Crowdsignal_Forms::init();
44 |
--------------------------------------------------------------------------------
/docker/.dockerignore:
--------------------------------------------------------------------------------
1 | data
2 | wordpress
3 | wordpress-develop
4 | develop
5 | logs
6 |
--------------------------------------------------------------------------------
/docker/bin/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if $(wp --allow-root core is-installed); then
4 | echo
5 | echo "WordPress has already been installed. Uninstall it first by running:"
6 | echo
7 | echo " make docker_uninstall"
8 | echo
9 | exit 1;
10 | fi
11 |
12 | # Install WP core
13 | wp --allow-root core install \
14 | --url=${WP_DOMAIN} \
15 | --title="${WP_TITLE}" \
16 | --admin_user=${WP_ADMIN_USER} \
17 | --admin_password=${WP_ADMIN_PASSWORD} \
18 | --admin_email=${WP_ADMIN_EMAIL} \
19 | --skip-email
20 |
21 | # Discourage search engines from indexing. Can be changed via UI in Settings->Reading.
22 | wp --allow-root option update blog_public 0
23 |
24 | # Install Query Monitor plugin
25 | # https://wordpress.org/plugins/query-monitor/
26 | wp --allow-root plugin install query-monitor --activate
27 |
28 | # Install Application Passwords for easy api access.
29 | wp --allow-root plugin install application-passwords --activate
30 | # Activate Crowdsignal Forms
31 | wp --allow-root plugin activate crowdsignal-forms
32 |
33 | echo
34 | echo "WordPress installed. Open ${WP_DOMAIN}"
35 | echo
36 |
--------------------------------------------------------------------------------
/docker/bin/install_composer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd /tmp
4 |
5 | EXPECTED_CHECKSUM="$(wget -q -O - https://composer.github.io/installer.sig)"
6 | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
7 | ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
8 |
9 | if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
10 | then
11 | >&2 echo 'ERROR: Invalid installer checksum'
12 | rm composer-setup.php
13 | exit 1
14 | fi
15 |
16 | php composer-setup.php --quiet
17 | RESULT=$?
18 | rm composer-setup.php
19 | exit $RESULT
20 |
--------------------------------------------------------------------------------
/docker/bin/multisite-convert.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if ! $(wp --allow-root core is-installed); then
4 | echo
5 | echo "WordPress has to be installed first. To install, run:"
6 | echo
7 | echo " yarn docker:install"
8 | echo
9 | exit 1;
10 | fi
11 |
12 | # Do the conversion, requires WP installed
13 | wp --allow-root core multisite-convert
14 |
15 | # Update domain to wp-config.php
16 | wp --allow-root config set DOMAIN_CURRENT_SITE "${WP_DOMAIN}" --type=constant
17 |
18 | # Use multisite htaccess template
19 | cp -f /tmp/htaccess-multi /var/www/html/.htaccess
20 |
21 | # Update domain to DB
22 | wp --allow-root db query "UPDATE wp_blogs SET domain='${WP_DOMAIN}' WHERE blog_id=1;"
23 |
24 | echo
25 | echo "WordPress converted to a multisite. Open ${WP_DOMAIN}"
26 | echo
27 |
--------------------------------------------------------------------------------
/docker/bin/tail.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | WP_DEBUG_LOG=/var/www/html/wp-content/debug.log
4 |
5 | if [ ! -e "$WP_DEBUG_LOG" ] ; then
6 | touch "$WP_DEBUG_LOG"
7 | fi
8 |
9 | tail -F --lines 100 "$WP_DEBUG_LOG"
10 |
--------------------------------------------------------------------------------
/docker/bin/uninstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Empty DB
4 | wp --allow-root db reset --yes
5 |
6 | # Ensure we have single-site htaccess instead of multisite,
7 | # just like we would have in fresh container.
8 | cp -f /tmp/htaccess /var/www/html/.htaccess
9 |
10 | # Remove "uploads" and "upgrade" folders
11 | rm -fr /var/www/html/wp-content/uploads /var/www/html/wp-content/upgrade
12 |
13 | # Empty WP debug log
14 | truncate -s 0 /var/www/html/wp-content/debug.log
15 |
16 | # Ensure wp-config.php doesn't have multi-site settings
17 | echo
18 | echo "Clearing out possible multi-site related settings from wp-config.php"
19 | echo "It's okay to see errors if these did't exist..."
20 | wp --allow-root config delete WP_ALLOW_MULTISITE
21 | wp --allow-root config delete MULTISITE
22 | wp --allow-root config delete SUBDOMAIN_INSTALL
23 | wp --allow-root config delete base
24 | wp --allow-root config delete DOMAIN_CURRENT_SITE
25 | wp --allow-root config delete PATH_CURRENT_SITE
26 | wp --allow-root config delete SITE_ID_CURRENT_SITE
27 | wp --allow-root config delete BLOG_ID_CURRENT_SITE
28 |
29 | echo
30 | echo "WordPress uninstalled. To install it again, run:"
31 | echo
32 | echo " yarn docker:install"
33 | echo
34 |
--------------------------------------------------------------------------------
/docker/config/apache_default:
--------------------------------------------------------------------------------
1 | ServerName localhost
2 |
3 |
4 | # The ServerName directive sets the request scheme, hostname and port that
5 | # the server uses to identify itself. This is used when creating
6 | # redirection URLs. In the context of virtual hosts, the ServerName
7 | # specifies what hostname must appear in the request's Host: header to
8 | # match this virtual host. For the default virtual host (this file) this
9 | # value is not decisive as it is used as a last resort host regardless.
10 | # However, you must set it for any further virtual host explicitly.
11 | ServerName localhost
12 |
13 | ServerAdmin webmaster@localhost
14 | DocumentRoot /var/www/html
15 |
16 |
17 | AllowOverride All
18 | Require all granted
19 |
20 |
21 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
22 | # error, crit, alert, emerg.
23 | # It is also possible to configure the loglevel for particular
24 | # modules, e.g.
25 | #LogLevel info ssl:warn
26 |
27 | # ErrorLog /var/log/apache-errors.log
28 | # CustomLog /var/log/apache-access.log combined
29 |
30 | # For most configuration files from conf-available/, which are
31 | # enabled or disabled at a global level, it is possible to
32 | # include a line for only one particular virtual host. For example the
33 | # following line enables the CGI configuration for this host only
34 | # after it has been globally disabled with "a2disconf".
35 | #Include conf-available/serve-cgi-bin.conf
36 |
37 |
38 |
39 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet
40 |
--------------------------------------------------------------------------------
/docker/config/htaccess:
--------------------------------------------------------------------------------
1 | # BEGIN WordPress
2 |
3 | RewriteEngine On
4 | RewriteBase /
5 | RewriteRule ^index\.php$ - [L]
6 | RewriteCond %{REQUEST_FILENAME} !-f
7 | RewriteCond %{REQUEST_FILENAME} !-d
8 | RewriteRule . /index.php [L]
9 |
10 | # END WordPress
11 |
--------------------------------------------------------------------------------
/docker/config/htaccess-multi:
--------------------------------------------------------------------------------
1 | # BEGIN WordPress
2 |
3 | RewriteEngine On
4 | RewriteBase /
5 | RewriteRule ^index\.php$ - [L]
6 |
7 | # add a trailing slash to /wp-admin
8 | RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
9 |
10 | RewriteCond %{REQUEST_FILENAME} -f [OR]
11 | RewriteCond %{REQUEST_FILENAME} -d
12 | RewriteRule ^ - [L]
13 | RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
14 | RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
15 | RewriteRule . index.php [L]
16 |
17 | # END WordPress
18 |
--------------------------------------------------------------------------------
/docker/config/php.ini:
--------------------------------------------------------------------------------
1 | short_open_tag = Off
2 | session.auto_start = Off
3 | file_uploads = On
4 | memory_limit = 64M
5 | upload_max_filesize = 64M
6 | post_max_size = 64M
7 | display_errors = On
8 | error_reporting = E_ALL
9 | error_log = /var/log/php/errors.log
10 | sendmail_path = /usr/sbin/ssmtp -t
11 | xdebug.remote_enable = On
12 | xdebug.remote_host = docker.for.mac.localhost
13 | xdebug.remote_handler = dbgp
14 | xdebug.profiler_enable = Off;
15 | xdebug.profiler_enable_trigger = On;
16 | xdebug.profiler_output_dir = "/var/www/html"
17 |
--------------------------------------------------------------------------------
/docker/config/ssmtp.conf:
--------------------------------------------------------------------------------
1 | #
2 | # Config file for sSMTP sendmail
3 | #
4 | # The person who gets all mail for userids < 1000
5 | # Make this empty to disable rewriting.
6 | root=postmaster
7 |
8 | # The place where the mail goes.
9 | mailhub=maildev:25
10 | #UseSTARTTLS=NO
11 | #AuthUser=
12 | #AuthPass=
13 |
14 | # Where will the mail seem to come from?
15 | #rewriteDomain=
16 |
17 | # The full hostname
18 | hostname=localhost
19 |
20 | # Are users allowed to set their own From: address?
21 | # YES - Allow the user to specify their own From: address
22 | # NO - Use the system generated From: address
23 | #FromLineOverride=YES
24 |
--------------------------------------------------------------------------------
/docker/data/mysql/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/docker/data/mysql/.gitkeep
--------------------------------------------------------------------------------
/docker/default.env:
--------------------------------------------------------------------------------
1 | # Default configuration for Docker containers.
2 | #
3 | # To modify, copy values over to ".env" file.
4 | # Values in ".env" file will override values
5 | # in "default.env".
6 | #
7 | # Values passed via command-line arguments take precedence over .env files:
8 | # $ WP_DOMAIN=example.com yarn docker:up
9 | #
10 | # Note that there is no special handling of quotation marks.
11 | # This means that they are part of the value.
12 | #
13 | # Note that these variables are not available in docker-compose.yml
14 | # Variables show up defined inside containers only.
15 |
16 | # WordPress - Only WP_ADMIN_PASSWORD needs to be changed
17 | WP_DOMAIN=localhost
18 | WP_ADMIN_USER=wordpress
19 | WP_ADMIN_EMAIL=wordpress@example.com
20 | # If this site is or will be publicly accessible, change WP_ADMIN_PASSWORD to something unique and secure
21 | WP_ADMIN_PASSWORD=wordpress
22 | WP_TITLE=HelloWord
23 |
24 | # Database - No changes necessary
25 | MYSQL_HOST=db:3306
26 | MYSQL_ROOT_PASSWORD=somewordpress
27 | MYSQL_DATABASE=wordpress
28 | MYSQL_USER=wordpress
29 | MYSQL_PASSWORD=wordpress
30 |
31 | # SFTP container users (user:pass:UID) - Password needs to be changed
32 | #
33 | # IMPORTANT: One of the users you define must be `wordpress` because paths in
34 | # `docker/docker-compose.yml` are fixed. You can modify their password, though.
35 | #
36 | # Set UID/GID manually for your users if you want them to make changes to
37 | # your mounted volumes with permissions matching your host filesystem.
38 | #
39 | # Define multiple users separated by space
40 | #
41 | # Read more: https://github.com/atmoz/sftp
42 | #
43 | # If this site is or will be publicly accessible, change the password below (the middle part) to something unique and secure
44 | SFTP_USERS=wordpress:wordpress:1001
45 |
46 | # Xdebug
47 | PHP_IDE_CONFIG=serverName=Test
48 |
49 | # The port to bind in the host machine.
50 | HOST_PORT=8000
51 |
52 | # Fill these with your own, and they will always override any other api keys set.
53 | CROWDSIGNAL_FORMS_API_PARTNER_GUID=''
54 | CROWDSIGNAL_FORMS_API_USER_CODE=''
55 |
56 |
57 |
--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 |
3 | volumes:
4 | ## For making sure the ./docker dir is not bound recursively.
5 | dockerdirectory:
6 |
7 | services:
8 | db:
9 | platform: linux/x86_64
10 | image: mysql:5.7
11 | container_name: cs_forms_mysql
12 | volumes:
13 | - ./data/mysql:/var/lib/mysql
14 | restart: always
15 | env_file:
16 | - default.env
17 | - .env
18 |
19 | wordpress:
20 | container_name: cs_forms_wordpress
21 | depends_on:
22 | - db
23 | build:
24 | context: .
25 | dockerfile: Dockerfile
26 | image: cs_forms_wordpress:localbuild
27 | volumes:
28 | - ..:/var/www/html/wp-content/plugins/crowdsignal-forms
29 | ## Kludge for not having docker contain recursive stuff
30 | ## You will see on your filesystem that this dir gets created
31 | - dockerdirectory:/var/www/html/wp-content/plugins/crowdsignal-forms/docker
32 | - ./mu-plugins:/var/www/html/wp-content/mu-plugins
33 | - ./wordpress-develop:/tmp/wordpress-develop
34 | - ./wordpress:/var/www/html
35 | - ./logs/apache2/:/var/log/apache2
36 | - ./logs/php:/var/log/php
37 | - ./bin:/var/scripts
38 | - ../../crowdsignal-plugin:/var/www/html/wp-content/plugins/polldaddy
39 | ports:
40 | - "${PORT_CS_WORDPRESS:-8000}:80"
41 | restart: always
42 | extra_hosts:
43 | - "api.crowdsignal.com:${CS_SANDBOX_IP:-192.0.123.248}"
44 | - "app.crowdsignal.com:${CS_SANDBOX_IP:-192.0.123.248}"
45 | - "api.polldaddy.com:${CS_SANDBOX_IP:-192.0.123.248}"
46 | env_file:
47 | - default.env
48 | - .env
49 |
--------------------------------------------------------------------------------
/docker/logs/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/docker/logs/.gitkeep
--------------------------------------------------------------------------------
/docker/mu-plugins/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 | !debug.php
4 | !avoid-plugin-deletion.php
5 | !0-force-crowdsignal-forms-api-keys.php
6 | !1-always-throw-cs-sync-errors.php
7 |
--------------------------------------------------------------------------------
/docker/mu-plugins/0-force-crowdsignal-forms-api-keys.php:
--------------------------------------------------------------------------------
1 | response[ $avoided_plugin ] ) ) {
55 | unset( $plugins->response[ $avoided_plugin ] );
56 | }
57 | }
58 | return $plugins;
59 | }
60 | add_filter( 'site_transient_update_plugins', 'jetpack_docker_disable_plugin_update' );
61 |
--------------------------------------------------------------------------------
/docker/wordpress-develop/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/docker/wordpress-develop/.gitkeep
--------------------------------------------------------------------------------
/docker/wordpress/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/docker/wordpress/.gitkeep
--------------------------------------------------------------------------------
/images/cs_dashboard_teaser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/images/cs_dashboard_teaser.png
--------------------------------------------------------------------------------
/includes/admin/class-crowdsignal-forms-admin.php:
--------------------------------------------------------------------------------
1 | setup_page = new Crowdsignal_Forms_Setup();
45 | $this->settings_page = new Crowdsignal_Forms_Settings();
46 | }
47 |
48 | /**
49 | * Set up actions during admin initialization.
50 | *
51 | * @todo for future use
52 | */
53 | public function admin_init() {
54 | add_filter( 'plugin_action_links_' . plugin_basename( CROWDSIGNAL_FORMS_PLUGIN_FILE ), array( $this, 'plugin_action_links' ) );
55 | }
56 |
57 | /**
58 | * Enqueues CSS and JS assets.
59 | *
60 | * @todo for future use
61 | */
62 | public function admin_enqueue_scripts() {
63 | }
64 |
65 | /**
66 | * Adds pages to admin menu.
67 | */
68 | public function admin_menu() {
69 | if (
70 | isset( $_GET['page'] )
71 | && ( 'crowdsignal-forms-settings' === $_GET['page'] || 'crowdsignal-forms-setup' === $_GET['page'] )
72 | ) {
73 | wp_safe_redirect( admin_url( 'options-general.php?page=crowdsignal-settings' ) );
74 | die();
75 | }
76 |
77 | if ( ! is_plugin_active( 'polldaddy/polldaddy.php' ) ) {
78 | // Add settings pages.
79 | add_options_page( 'Crowdsignal', 'Crowdsignal', 'manage_options', 'crowdsignal-settings', array( $this->settings_page, 'output' ) );
80 | }
81 | }
82 |
83 | /**
84 | * Adds to the Action links in the plugin page.
85 | *
86 | * @param array $links
87 | * @return array
88 | */
89 | public function plugin_action_links( $links ) {
90 | return array_merge(
91 | array(
92 | sprintf( '' . __( 'Settings', 'crowdsignal-forms' ) . ' ', admin_url( 'options-general.php?page=crowdsignal-settings' ) ),
93 | ),
94 | $links
95 | );
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/includes/admin/class-crowdsignal-forms-notice-icon.php:
--------------------------------------------------------------------------------
1 | ' . self::svg_icon_warning() . '';
25 | }
26 |
27 | /**
28 | * Returns the success svg icon wrapped in a span tag
29 | */
30 | public static function success() {
31 | return '' . self::svg_icon_success() . ' ';
32 | }
33 |
34 | /**
35 | * Returns the warning svg icon markup
36 | */
37 | private static function svg_icon_warning() {
38 | return '
39 |
40 |
41 |
42 |
43 |
44 |
45 | ';
46 | }
47 |
48 | /**
49 | * Returns the success svg icon markup
50 | */
51 | private static function svg_icon_success() {
52 | return '
53 |
54 |
55 |
56 |
57 |
58 |
59 | ';
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/includes/admin/index.php:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 | Crowdsignal.', 'crowdsignal-forms' ) );
18 | ?>
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/includes/admin/views/html-admin-setup-step-2.php:
--------------------------------------------------------------------------------
1 | get_api_key() ) {
15 | $crowdsignal_forms_msg = 'connected';
16 | } else {
17 | $crowdsignal_forms_msg = 'api-key-not-added';
18 | }
19 | ?>
20 |
28 |
29 |
--------------------------------------------------------------------------------
/includes/admin/views/html-admin-setup-step-3.php:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
19 |
20 |
52 |
--------------------------------------------------------------------------------
/includes/auth/class-api-auth-provider-interface.php:
--------------------------------------------------------------------------------
1 | array(
39 | 'partnerGUID' => $api_key,
40 | 'partnerUserID' => wp_get_current_user()->ID,
41 | 'demands' => array(
42 | 'demand' => array(
43 | 'id' => 'GetUserCode',
44 | ),
45 | ),
46 | ),
47 | )
48 | );
49 |
50 | $data = $this->perform_query( $curl_data );
51 |
52 | if ( isset( $data->pdResponse->userCode ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- data from API.
53 | return $data->pdResponse->userCode; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- data from API.
54 | } else {
55 | return false;
56 | }
57 | }
58 |
59 | /**
60 | * Get the Crowdsignal user code for an API key.
61 | *
62 | * @param string $query query to send to API.
63 | * @return mixed
64 | */
65 | private function perform_query( $query ) {
66 | $data = wp_remote_post(
67 | 'https://api.crowdsignal.com/v1',
68 | array(
69 | 'method' => 'POST',
70 | 'body' => $query,
71 | 'headers' => array( 'Content-Type' => 'application/json' ),
72 | )
73 | );
74 | if ( is_wp_error( $data ) ) {
75 | return array();
76 | }
77 | return json_decode( $data['body'] );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/includes/frontend/class-crowdsignal-forms-blocks.php:
--------------------------------------------------------------------------------
1 | 0 ) {
39 | return self::$blocks;
40 | }
41 |
42 | self::$blocks = array(
43 | new Blocks\Crowdsignal_Forms_Poll_Block(),
44 | new Blocks\Crowdsignal_Forms_Vote_Block(),
45 | new Blocks\Crowdsignal_Forms_Vote_Item_Block(),
46 | new Blocks\Crowdsignal_Forms_Applause_Block(),
47 | new Blocks\Crowdsignal_Forms_Nps_Block(),
48 | new Blocks\Crowdsignal_Forms_Feedback_Block(),
49 | );
50 |
51 | return self::$blocks;
52 | }
53 |
54 | /**
55 | * Registers Crowdsignal Forms' custom Gutenberg blocks
56 | */
57 | public function register() {
58 | foreach ( self::blocks() as $block ) {
59 | $block->register();
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/includes/frontend/index.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | A custom set of code standard rules to check for WordPress themes and plugins.
4 |
5 |
6 |
7 |
8 |
9 |
10 | .
11 |
12 | ^node_modules/*
13 | ^vendor/*
14 | ^build/*
15 | tests/
16 |
17 |
18 | ^templates/*
19 | ^lib/*
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | includes/**/abstract-*.php
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | tests/
55 |
56 |
57 |
58 | tests/*
59 |
60 |
61 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ./tests/unit-tests
20 |
21 |
22 |
23 |
24 | ./includes
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/scripts/makepot.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # to be ran within a docker instance or somewhere where wp-cli is installed.
4 | # requires gettext.
5 | # usually ran via make translations
6 |
7 | set -e
8 |
9 | composer exec -v -- 'wp i18n make-pot . ./languages/crowdsignal-forms.pot --exclude="docker,tests,release,client"'
10 |
--------------------------------------------------------------------------------
/scripts/package-for-release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 |
6 | command -v zip || {
7 | >&2 echo "zip is required"
8 | exit 1
9 | }
10 | command -v composer || {
11 | >&2 echo "composer is required"
12 | exit 1
13 | }
14 | command -v git || {
15 | >&2 echo "git is required"
16 | exit 1
17 | }
18 |
19 | PLUGIN_DIR=`pwd`
20 | RELEASE_ZIP_FILENAME="crowdsignal-forms.$(git rev-parse --abbrev-ref HEAD | sed 's/\//-/g' | tr -d '\n').zip"
21 | RELEASE_FOLDER="/tmp/crowdsignal-forms-release"
22 | RELEASE_BUILD_FOLDER="$RELEASE_FOLDER/crowdsignal-forms"
23 |
24 | rm -rf "$RELEASE_FOLDER"
25 | mkdir -p "$RELEASE_BUILD_FOLDER"
26 |
27 | cp -r "$PLUGIN_DIR/includes" "$RELEASE_BUILD_FOLDER"
28 | cp -r "$PLUGIN_DIR/build" "$RELEASE_BUILD_FOLDER"
29 | cp -r "$PLUGIN_DIR/languages" "$RELEASE_BUILD_FOLDER"
30 | cp -r "$PLUGIN_DIR/changelog.txt" "$RELEASE_BUILD_FOLDER"
31 | cp -r "$PLUGIN_DIR/index.php" "$RELEASE_BUILD_FOLDER"
32 | cp -r "$PLUGIN_DIR/LICENSE.TXT" "$RELEASE_BUILD_FOLDER"
33 | cp -r "$PLUGIN_DIR/README.TXT" "$RELEASE_BUILD_FOLDER"
34 | cp -r "$PLUGIN_DIR/crowdsignal-forms.php" "$RELEASE_BUILD_FOLDER"
35 | cp -r "$PLUGIN_DIR/uninstall.php" "$RELEASE_BUILD_FOLDER"
36 |
37 | rm -f "$RELEASE_BUILD_FOLDER/includes/gateways/class-canned-api-gateway.php"
38 |
39 | mkdir -p "$PLUGIN_DIR/release"
40 | cd "$RELEASE_FOLDER" && zip -r "$PLUGIN_DIR/release/$RELEASE_ZIP_FILENAME" "crowdsignal-forms"
41 |
42 | echo "Release zip: $PLUGIN_DIR/release/$RELEASE_ZIP_FILENAME"
43 | rm -rf "$RELEASE_BUILD_FOLDER"
44 |
--------------------------------------------------------------------------------
/tests-js/mocks/blocks.js:
--------------------------------------------------------------------------------
1 | export const registerBlockStyle = ( blockName, styleObject ) => null;
2 |
3 | export const unregisterBlockStyle = ( blockName, styleName ) => null;
4 |
--------------------------------------------------------------------------------
/tests-js/mocks/i18n.js:
--------------------------------------------------------------------------------
1 | export const __ = ( string ) => string;
2 |
3 | export const _n = ( string ) => string;
4 |
5 | export const sprintf = ( string ) => string;
6 |
--------------------------------------------------------------------------------
/tests/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/crowdsignal-forms/e0084d0760a16babe9ae441e24de3f4910d57b27/tests/.keep
--------------------------------------------------------------------------------
/tests/canned-data/api-data.json:
--------------------------------------------------------------------------------
1 | {
2 | "polls": [
3 | {
4 | "id": 1,
5 | "question": "What is your favorite color?",
6 | "answers": [
7 | {
8 | "id": 123,
9 | "answer_text": "blue",
10 | "answer_count": 42
11 | },
12 | {
13 | "id": 1234,
14 | "answer_text": "green",
15 | "answer_count": 11
16 | }
17 | ]
18 | },
19 | {
20 | "id": 2,
21 | "question": "What is your favorite 80's disco song?",
22 | "answers": [
23 | {
24 | "id": 321,
25 | "answer_text": "Disco was in the 70s!",
26 | "answer_count": 42
27 | },
28 | {
29 | "id": 3210,
30 | "answer_text": "None of the above.",
31 | "answer_count": 11
32 | }
33 | ]
34 | }
35 | ],
36 | "capabilities": [
37 | "unlimited-questions",
38 | "device-report",
39 | "unlimited-email-responses",
40 | "custom-style",
41 | "feature-poll",
42 | "feature-rating",
43 | "feature-survey",
44 | "feature-quiz",
45 | "dashboard",
46 | "dashboard-migrate",
47 | "map-domain",
48 | "custom-email-notification-address",
49 | "export-pdf",
50 | "export",
51 | "whitelist-maximum-polls",
52 | "survey-custom-finish",
53 | "ssl",
54 | "email",
55 | "hide-branding",
56 | "survey-custom-url",
57 | "survey-responses",
58 | "filter",
59 | "share",
60 | "poll-reports",
61 | "public-json-polls",
62 | "survey-restrictions",
63 | "poll-results-embed",
64 | "poll-restrictions",
65 | "sync-to-external-services",
66 | "survey-timeout"
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/tests/canned-data/block-data-empty.json:
--------------------------------------------------------------------------------
1 | {
2 | "pollId": 1,
3 | "isMultipleChoice": false,
4 | "title": "Untitled Poll",
5 | "question": "",
6 | "note": "",
7 | "answers": [
8 | {
9 | "answerId": 1,
10 | "text": ""
11 | },
12 | {
13 | "answerId": 2,
14 | "text": ""
15 | }
16 | ],
17 | "submitButtonLabel": "Submit",
18 | "submitButtonTextColor": "",
19 | "submitButtonBackgroundColor": "",
20 | "confirmMessageType": "results",
21 | "customConfirmMessage": "",
22 | "redirectAddress": "",
23 | "textColor": "",
24 | "backgroundColor": "",
25 | "borderColor": "",
26 | "borderWidth": 2,
27 | "borderRadius": 0,
28 | "hasBoxShadow": true,
29 | "fontFamily": null,
30 | "hasOneResponsePerComputer": false,
31 | "randomizeAnswers": false,
32 | "blockAlignment": "center",
33 | "pollStatus": "open",
34 | "closedPollState": "show-results",
35 | "closedAfterDateTime": null
36 | }
37 |
--------------------------------------------------------------------------------
/tests/framework/class-crowdsignal-forms-unit-test-case.php:
--------------------------------------------------------------------------------
1 | default_user_id = get_current_user_id();
18 | }
19 |
20 | /**
21 | * Retrieve a test user of a particular role.
22 | *
23 | * @param string $role Role of the user to create.
24 | * @return int
25 | */
26 | protected function get_user_by_role( $role ) {
27 | $user_prefix = 'crowdsignal_forms_';
28 | $user = get_user_by( 'email', $user_prefix . $role . '_user@example.com' );
29 | if ( empty( $user ) ) {
30 | $user_id = wp_create_user(
31 | $user_prefix . $role . '_user',
32 | $user_prefix . $role . '_user',
33 | $user_prefix . $role . '_user@example.com'
34 | );
35 | $user = get_user_by( 'ID', $user_id );
36 | $user->set_role( $role );
37 | }
38 | return $user->ID;
39 | }
40 |
41 | /**
42 | * Login as an admin user.
43 | *
44 | * @return self
45 | */
46 | protected function login_as_admin() {
47 | return $this->login_as( $this->get_user_by_role( 'administrator' ) );
48 | }
49 |
50 | /**
51 | * Login as an editor user.
52 | *
53 | * @return self
54 | */
55 | protected function login_as_editor() {
56 | return $this->login_as( $this->get_user_by_role( 'editor' ) );
57 | }
58 |
59 | /**
60 | * Login as the default user.
61 | *
62 | * @return self
63 | */
64 | protected function login_as_default_user() {
65 | return $this->login_as( $this->default_user_id );
66 | }
67 |
68 | /**
69 | * Login as a particular user.
70 | *
71 | * @param int $user_id ID for the user to login as.
72 | * @return self
73 | */
74 | protected function login_as( $user_id ) {
75 | wp_set_current_user( $user_id );
76 | return $this;
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/tests/unit-tests/includes/auth/test-class.crowdsignal-forms-api-auth-test.php:
--------------------------------------------------------------------------------
1 | assertEquals( 'test-user-code', $cs_auth->get_user_code() );
33 | }
34 | }
35 |
36 | class TestProvider implements Api_Auth_Provider_Interface {
37 |
38 | public function get_user_code( $user_id ) {
39 | return 'test-user-code';
40 | }
41 |
42 | /**
43 | * @inheritDoc
44 | */
45 | public function fetch_user_code( $user_id ) {
46 | // TODO: Implement fetch_user_code() method.
47 | return 'test-user-code';
48 | }
49 |
50 | /**
51 | * @inheritDoc
52 | */
53 | public function fetch_user_code_for_key( $api_key ) {
54 | // TODO: Implement fetch_user_code_for_key() method.
55 | return 'test-user-code';
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/unit-tests/includes/gateways/test-class.api-gateway-interface.php:
--------------------------------------------------------------------------------
1 | assertTrue( interface_exists('\Crowdsignal_Forms\Gateways\Api_Gateway_Interface' ) );
22 | }
23 |
24 | /**
25 | * @covers \Crowdsignal_Forms\Gateways\Api_Gateway_Interface::get_poll\
26 | *
27 | * @since 0.9.0
28 | */
29 | public function testInterfaceDefinesGetPoll() {
30 | $this->assertTrue( method_exists('\Crowdsignal_Forms\Gateways\Api_Gateway_Interface', 'get_poll' ) );
31 | }
32 |
33 | /**
34 | * @covers \Crowdsignal_Forms\Gateways\Api_Gateway_Interface::create_poll
35 | *
36 | * @since 0.9.0
37 | */
38 | public function testInterfaceDefinesCreatePoll() {
39 | $this->assertTrue( method_exists('\Crowdsignal_Forms\Gateways\Api_Gateway_Interface', 'create_poll' ) );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/unit-tests/includes/gateways/test-class.api-gateway.php:
--------------------------------------------------------------------------------
1 | assertTrue( class_exists('\Crowdsignal_Forms\Gateways\Api_Gateway' ) );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/unit-tests/includes/gateways/test-class.canned-api-gateway.php:
--------------------------------------------------------------------------------
1 | assertTrue( class_exists('\Crowdsignal_Forms\Gateways\Canned_Api_Gateway' ) );
21 | }
22 |
23 | /**
24 | * @covers \Crowdsignal_Forms\Gateways\Canned_Api_Gateway::get_poll
25 | *
26 | * @since 0.9.0
27 | */
28 | public function test_get_poll_returns_poll_if_in_canned_data() {
29 | $gateway = new Gateways\Canned_Api_Gateway();
30 | $poll = $gateway->get_poll( 1 );
31 | $this->assertTrue( ! is_wp_error( $poll ) );
32 | }
33 |
34 | /**
35 | * @covers \Crowdsignal_Forms\Gateways\Canned_Api_Gateway::get_poll
36 | *
37 | * @since 0.9.0
38 | */
39 | public function test_get_poll_returns_error_if_not_in_canned_data() {
40 | $gateway = new Gateways\Canned_Api_Gateway();
41 | $poll = $gateway->get_poll( 666 );
42 | $this->assertTrue( is_wp_error( $poll ) );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/unit-tests/includes/models/test-class.poll-settings.php:
--------------------------------------------------------------------------------
1 | tests_dir;
30 | $data = file_get_contents( $tests_dir . '/canned-data/block-data-empty.json' );
31 | $poll_settings = Poll_Settings::from_array( json_decode( $data, true ) );
32 | $this->assertTrue( is_a( $poll_settings, Poll_Settings::class ) );
33 | $poll_array = $poll_settings->to_array();
34 | $this->assertTrue( array_key_exists( 'title', $poll_array ), 'Poll array should have a "title" prop' );
35 | $this->assertTrue( array_key_exists( 'after_vote', $poll_array ), 'Poll array should have a "after_vote" prop' );
36 | $this->assertTrue( array_key_exists( 'after_message', $poll_array ), 'Poll array should have a "after_message" prop' );
37 | $this->assertTrue( array_key_exists( 'redirect_url', $poll_array ), 'Poll array should have a "redirect_url" prop' );
38 | $this->assertTrue( array_key_exists( 'randomize_answers', $poll_array ), 'Poll array should have a "randomize_answers" prop' );
39 | $this->assertTrue( array_key_exists( 'restrict_vote_repeat', $poll_array ), 'Poll array should have a "restrict_vote_repeat" prop' );
40 | $this->assertTrue( array_key_exists( 'captcha', $poll_array ), 'Poll array should have a "captcha" prop' );
41 | $this->assertTrue( array_key_exists( 'multiple_choice', $poll_array ), 'Poll array should have a "multiple_choice" prop' );
42 | $this->assertTrue( array_key_exists( 'close_status', $poll_array ), 'Poll array should have a "close_status" prop' );
43 | $this->assertTrue( array_key_exists( 'close_after', $poll_array ), 'Poll array should have a "close_after" prop' );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/unit-tests/includes/models/test-class.poll.php:
--------------------------------------------------------------------------------
1 | assertTrue( is_a( $poll,Poll::class ) );
31 | $this->assertSame( 0, $poll->get_id() );
32 | $this->assertSame( '', $poll->get_question() );
33 | $this->assertSame( 0, count( $poll->get_answers() ) );
34 | }
35 |
36 | /**
37 | * @covers \Crowdsignal_Forms\Rest_Api\Controllers\Poll::from_array
38 | *
39 | * @since 0.9.0
40 | */
41 | public function test_from_array() {
42 | $data = array(
43 | 'answers' => array(),
44 | 'settings' => array(),
45 | 'id' => 1,
46 | 'question' => 'Best Sci-fi film ever?'
47 | );
48 | $poll = Poll::from_array( $data );
49 | $this->assertTrue( is_a( $poll,Poll::class ) );
50 | $this->assertSame( 1, $poll->get_id() );
51 | $this->assertSame( 'Best Sci-fi film ever?', $poll->get_question() );
52 | $this->assertSame( 0, count( $poll->get_answers() ) );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/unit-tests/includes/rest-api/controllers/test-class.account-controller.php:
--------------------------------------------------------------------------------
1 | server = $wp_rest_server;
31 |
32 | do_action( 'rest_api_init' );
33 | $this->controller = new Account_Controller();
34 | }
35 |
36 | /**
37 | * Test specific teardown.
38 | * @since 0.9.0
39 | */
40 | public function tearDown() {
41 | parent::tearDown();
42 |
43 | global $wp_rest_server;
44 | $wp_rest_server = null;
45 | }
46 |
47 | /**
48 | * @covers \Crowdsignal_Forms\Rest_Api\Controllers\Polls_Controller::get_poll
49 | *
50 | * @since 0.9.0
51 | */
52 | public function test_get_capabilities() {
53 | Crowdsignal_Forms\Crowdsignal_Forms::instance()->set_api_gateway( new Canned_Api_Gateway() );
54 | $req = new \WP_REST_Request( 'GET', '/account/capabilities' );
55 | $response = $this->controller->get_capabilities( $req );
56 | $this->assertTrue( is_a( $response, \WP_REST_Response::class ) );
57 | $this->assertTrue( $response->get_status() === 200 );
58 | $this->assertTrue( in_array( 'hide-branding', $response->data ) );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/uninstall.php:
--------------------------------------------------------------------------------
1 |
33 | plugin.constructor.name !==
34 | 'DependencyExtractionWebpackPlugin' &&
35 | plugin.constructor.name !== 'CleanWebpackPlugin'
36 | ),
37 | new DependencyExtractionWebpackPlugin( {
38 | injectPolyfill: true,
39 | requestToExternal: ( request ) => {
40 | if ( request === '@crowdsignalForms/apifetch' ) {
41 | return [ 'crowdsignalForms', 'apiFetch' ];
42 | }
43 | },
44 | requestToHandle: ( request ) => {
45 | // These values must match the names defined in class-crowdsignal-forms-blocks-assets.php
46 | if ( request === '@crowdsignalForms/apifetch' ) {
47 | return 'crowdsignal-forms-apifetch';
48 | }
49 | },
50 | } ),
51 | ],
52 | externals: {
53 | ...webpackConfig.externals,
54 | jquery: 'jQuery',
55 | react: 'React',
56 | 'react-dom': 'ReactDOM',
57 | lodash: 'lodash',
58 | },
59 | };
60 |
61 | return config;
62 | };
63 |
--------------------------------------------------------------------------------