Compare commits
49 Commits
Author | SHA1 | Date |
---|---|---|
|
d2670bfcc7 | |
|
68abe08e4a | |
|
b44e8495ab | |
|
20152da6f4 | |
|
3e43ea9c52 | |
|
d9878e7a5a | |
|
e9a9d9e6dd | |
|
26d1a7b78f | |
|
0b1fe630b3 | |
|
fa1ce8f041 | |
|
e2d014a03c | |
|
24cbe3c071 | |
|
85a9c15083 | |
|
12ac84d2b7 | |
|
d38a6298b2 | |
|
813a4326d1 | |
|
d1827030ed | |
|
3a1f570cb5 | |
|
ec3180f86c | |
|
39facd1161 | |
|
1d1a40c074 | |
|
1b8c4596c0 | |
|
2629a30c1c | |
|
1b7631d4b8 | |
|
b56a11afec | |
|
4429576bdc | |
|
b6d63a55f2 | |
|
965f135474 | |
|
ee475f2e2a | |
|
4484f342a5 | |
|
d2ee8f0af6 | |
|
22852dd76d | |
|
e9ccb0b239 | |
|
4131728309 | |
|
4070a2e443 | |
|
393162059f | |
|
291f06be8c | |
|
df5e982181 | |
|
a872f419c6 | |
|
10623f82e2 | |
|
485bf919ed | |
|
389cf41a25 | |
|
e4fad93844 | |
|
235987623d | |
|
b9d5e0d5a8 | |
|
82c03f0424 | |
|
25011279c7 | |
|
ab6600dbd1 | |
|
5894dec4b8 |
11
.env
11
.env
|
@ -15,3 +15,14 @@ WORDPRESS_DEBUG=true
|
|||
WORDPRESS_DEBUG_LOG=true
|
||||
FONTAWESOME_ENV=development
|
||||
MYSQL_ROOT_PASSWORD=somewordpress
|
||||
# This should be false by default.
|
||||
# It could be overridden in .env.local.
|
||||
# This pertains to the mod_security OWASP core rule set.
|
||||
ENABLE_MOD_SECURITY=false
|
||||
|
||||
# ENABLE_MOD_SECURITY=true, then the following
|
||||
# may be set to "true" to make exceptions to the usual mod_security rules,
|
||||
# allowing any requests on the font-awesome REST API routes that would
|
||||
# otherwise be blocked by the core rule set.
|
||||
#
|
||||
# ALLOW_ALL_REQUESTS_FOR_FONT_AWESOME=true
|
||||
|
|
|
@ -2,33 +2,33 @@ name: Jest
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches:
|
||||
- "**"
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
jest:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '18'
|
||||
check-latest: true
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "18"
|
||||
check-latest: true
|
||||
|
||||
- name: Cache node_module
|
||||
id: node-modules-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: admin/node_modules
|
||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('admin/package-lock.json') }}
|
||||
- name: Cache node_module
|
||||
id: node-modules-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: admin/node_modules
|
||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('admin/package-lock.json') }}
|
||||
|
||||
- name: Install node dependencies
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
run: cd admin && npm install
|
||||
- name: Install node dependencies
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
run: cd admin && npm install
|
||||
|
||||
- name: Run Jest
|
||||
run: cd admin && npm run test
|
||||
- name: Run Jest
|
||||
run: cd admin && npm run test
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
name: PHP Composer
|
||||
name: PHP Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches:
|
||||
- "**"
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
|
||||
env:
|
||||
# Seems like we should be able to use these in the services section below
|
||||
|
@ -18,257 +19,257 @@ env:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: somewordpress
|
||||
MYSQL_DATABASE: wordpressdb
|
||||
MYSQL_USER: wordpress
|
||||
MYSQL_PASSWORD: password
|
||||
ports:
|
||||
- 3306:3306
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: somewordpress
|
||||
MYSQL_DATABASE: wordpressdb
|
||||
MYSQL_USER: wordpress
|
||||
MYSQL_PASSWORD: password
|
||||
ports:
|
||||
- 3306:3306
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
|
||||
wordpress: [latest]
|
||||
include:
|
||||
- php: '5.6'
|
||||
wordpress: 5.2.5
|
||||
- php: '7.4'
|
||||
wordpress: trunk
|
||||
matrix:
|
||||
php: ["7.4", "8.0", "8.1", "8.2", "8.3"]
|
||||
wordpress: [latest]
|
||||
include:
|
||||
- php: "8.3"
|
||||
wordpress: trunk
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: uopz
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
id: composer-lock
|
||||
run: |
|
||||
if [ ${{ matrix.php }} == '7.4' ]; then
|
||||
composer validate
|
||||
LOCK_FILE=composer.lock
|
||||
COMPOSER_FILE=composer.json
|
||||
else
|
||||
COMPOSER=composer-php${{ matrix.php }}.json composer validate
|
||||
LOCK_FILE=composer-php${{ matrix.php }}.lock
|
||||
COMPOSER_FILE=composer-php${{ matrix.php }}.json
|
||||
fi
|
||||
echo "COMPOSER_LOCK_HASH=$(md5sum $LOCK_FILE | cut -d' ' -f1)" >> $GITHUB_OUTPUT
|
||||
echo "COMPOSER_FILE=${COMPOSER_FILE}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Resolve Cache Date
|
||||
id: cache-date
|
||||
run: echo "DATE=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Composer packages
|
||||
id: composer-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: vendor
|
||||
key: ${{ runner.os }}-php-b-${{ steps.composer-lock.outputs.COMPOSER_LOCK_HASH }}
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.composer-cache.outputs.cache-hit != 'true'
|
||||
run: COMPOSER=${{ steps.composer-lock.outputs.COMPOSER_FILE }} composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Cache APT Sources Daily
|
||||
id: apt-sources-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/apt-sources-${{ steps.cache-date.outputs.DATE }}
|
||||
key: ${{ runner.os }}-apt-sources-${{ steps.cache-date.outputs.DATE }}
|
||||
|
||||
- name: Cache APT Packages Daily
|
||||
id: apt-packages-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/apt-packages-${{ steps.cache-date.outputs.DATE }}
|
||||
key: ${{ runner.os }}-apt-packages-${{ steps.cache-date.outputs.DATE }}
|
||||
|
||||
- name: Update APT Sources
|
||||
if: steps.apt-sources-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
mkdir -p /tmp/apt-sources-${{ steps.cache-date.outputs.DATE }}
|
||||
sudo cp -R /var/lib/apt/lists/* /tmp/apt-sources-${{ steps.cache-date.outputs.DATE }}
|
||||
|
||||
- name: Download APT Packages
|
||||
if: steps.apt-packages-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
sudo cp -R /tmp/apt-sources-${{ steps.cache-date.outputs.DATE }}/* /var/lib/apt/lists
|
||||
sudo apt-get install -y --download-only subversion mysql-client
|
||||
sudo mkdir -p /tmp/apt-packages-${{ steps.cache-date.outputs.DATE }}
|
||||
sudo cp -R /var/cache/apt/archives/*.deb /tmp/apt-packages-${{ steps.cache-date.outputs.DATE }}
|
||||
|
||||
- name: Install OS Packages
|
||||
run: sudo dpkg -i /tmp/apt-packages-${{ steps.cache-date.outputs.DATE }}/*.deb
|
||||
|
||||
- name: Verify DB
|
||||
run: mysql --user=root --password=${MYSQL_ROOT_PASSWORD} --host=${MYSQL_HOST} --port=${MYSQL_PORT} --protocol=tcp -e 'SHOW DATABASES;'
|
||||
|
||||
- name: Resolve WordPress Version
|
||||
run: |
|
||||
curl -s https://api.wordpress.org/core/version-check/1.7/ > /tmp/wp-latest.json
|
||||
LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//' | head -1)
|
||||
if [ "${{ matrix.wordpress }}" == 'latest' ]; then
|
||||
VERSION=$LATEST_VERSION
|
||||
echo "WORDPRESS_VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_VERSION_IS_TRUNK=0" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_CORE_DIR=/tmp/$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WP_TESTS_TAG=branches/$VERSION" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ matrix.wordpress }}" == 'trunk' ]; then
|
||||
VERSION=trunk-$(date +'%Y-%m-%d')
|
||||
echo "WORDPRESS_VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_VERSION_IS_TRUNK=1" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_CORE_DIR=/tmp/$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WP_TESTS_TAG=trunk" >> $GITHUB_OUTPUT
|
||||
else
|
||||
VERSION=${{ matrix.wordpress }}
|
||||
echo "WORDPRESS_VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_VERSION_IS_TRUNK=0" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_CORE_DIR=/tmp/$VERSION" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [[ $VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
|
||||
WP_BRANCH=${VERSION%\-*}
|
||||
echo "WP_TESTS_TAG=branches/$WP_BRANCH" >> $GITHUB_OUTPUT
|
||||
elif [[ $VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "WP_TESTS_TAG=branches/$VERSION" >> $GITHUB_OUTPUT
|
||||
elif [[ $VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||||
if [[ $VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
|
||||
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
|
||||
echo "WP_TESTS_TAG=tags/${VERSION%??}" >> $GITHUB_OUTPUT
|
||||
- name: Validate composer.json and composer.lock
|
||||
id: composer-lock
|
||||
run: |
|
||||
if [ ${{ matrix.php }} == '8.2' ]; then
|
||||
composer validate
|
||||
LOCK_FILE=composer.lock
|
||||
COMPOSER_FILE=composer.json
|
||||
else
|
||||
echo "WP_TESTS_TAG=tags/$VERSION" >> $GITHUB_OUTPUT
|
||||
COMPOSER=composer-php${{ matrix.php }}.json composer validate
|
||||
LOCK_FILE=composer-php${{ matrix.php }}.lock
|
||||
COMPOSER_FILE=composer-php${{ matrix.php }}.json
|
||||
fi
|
||||
elif [[ $VERSION == 'nightly' || $VERSION == 'trunk' ]]; then
|
||||
echo "WP_TESTS_TAG=trunk" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "WP_TESTS_TAG=tags/$LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "WP_TESTS_DIR=/tmp/test/$VERSION" >> $GITHUB_OUTPUT
|
||||
id: wordpress-version
|
||||
echo "COMPOSER_LOCK_HASH=$(md5sum $LOCK_FILE | cut -d' ' -f1)" >> $GITHUB_OUTPUT
|
||||
echo "COMPOSER_FILE=${COMPOSER_FILE}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Show WordPress Version
|
||||
run: echo "The current WordPress version is ${{ steps.wordpress-version.outputs.WORDPRESS_VERSION }}, and WP_TESTS_TAG=${{ steps.wordpress-version.outputs.WP_TESTS_TAG }}"
|
||||
- name: Resolve Cache Date
|
||||
id: cache-date
|
||||
run: echo "DATE=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache WordPress Core Installation
|
||||
id: wordpress-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}
|
||||
key: ${{ runner.os }}-wordpress-${{ steps.wordpress-version.outputs.WORDPRESS_VERSION }}') }}
|
||||
- name: Cache Composer packages
|
||||
id: composer-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: vendor
|
||||
key: ${{ runner.os }}-php-b-${{ steps.composer-lock.outputs.COMPOSER_LOCK_HASH }}
|
||||
|
||||
- name: Install WordPress Core
|
||||
if: steps.wordpress-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}
|
||||
if [ "${{ steps.wordpress-version.outputs.WORDPRESS_VERSION_IS_TRUNK }}" == "1" ]; then
|
||||
mkdir -p /tmp/wordpress-nightly
|
||||
curl https://wordpress.org/nightly-builds/wordpress-latest.zip > /tmp/wordpress-nightly.zip
|
||||
unzip -q /tmp/wordpress-nightly.zip -d /tmp/wordpress-nightly/
|
||||
mv /tmp/wordpress-nightly/wordpress/* ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}
|
||||
else
|
||||
curl -s https://wordpress.org/wordpress-${{ steps.wordpress-version.outputs.WORDPRESS_VERSION }}.tar.gz > /tmp/wordpress.tar.gz
|
||||
tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}
|
||||
fi
|
||||
curl https://raw.github.com/markoheijnen/wp-mysqli/master/db.php > ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}/wp-content/db.php
|
||||
- name: Install dependencies
|
||||
if: steps.composer-cache.outputs.cache-hit != 'true'
|
||||
run: COMPOSER=${{ steps.composer-lock.outputs.COMPOSER_FILE }} composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Cache WordPress Test Installation
|
||||
id: wordpress-test-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.wordpress-version.outputs.WP_TESTS_DIR }}
|
||||
key: ${{ runner.os }}-wordpress-test-${{ steps.wordpress-version.outputs.WORDPRESS_VERSION }}') }}
|
||||
- name: Cache APT Sources Daily
|
||||
id: apt-sources-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/apt-sources-${{ steps.cache-date.outputs.DATE }}
|
||||
key: ${{ runner.os }}-apt-sources-${{ steps.cache-date.outputs.DATE }}
|
||||
|
||||
- name: Install WordPress Test
|
||||
if: steps.wordpress-test-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p ${{ steps.wordpress-version.outputs.WP_TESTS_DIR }}
|
||||
DB_USER=${MYSQL_USER}
|
||||
DB_PASS=${MYSQL_PASSWORD}
|
||||
DB_NAME=${MYSQL_DATABASE}
|
||||
DB_HOST=${MYSQL_HOST}:${MYSQL_PORT}
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }}
|
||||
svn co --quiet https://develop.svn.wordpress.org/${{ steps.wordpress-version.outputs.WP_TESTS_TAG }}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
|
||||
svn co --quiet https://develop.svn.wordpress.org/${{ steps.wordpress-version.outputs.WP_TESTS_TAG }}/tests/phpunit/data/ $WP_TESTS_DIR/data
|
||||
curl https://develop.svn.wordpress.org/${{ steps.wordpress-version.outputs.WP_TESTS_TAG }}/wp-tests-config-sample.php > $WP_TESTS_DIR/wp-tests-config.php
|
||||
# remove all forward slashes in the end
|
||||
WP_CORE_DIR=$(echo ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }} | sed "s:/\+$::")
|
||||
sed -i "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed -i "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed -i "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed -i "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed -i "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
- name: Cache APT Packages Daily
|
||||
id: apt-packages-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/apt-packages-${{ steps.cache-date.outputs.DATE }}
|
||||
key: ${{ runner.os }}-apt-packages-${{ steps.cache-date.outputs.DATE }}
|
||||
|
||||
- name: Run PHPUnit Main Tests
|
||||
# only run the output tests on the newer versions of php and phpunit, cause
|
||||
# they're trickier
|
||||
run: |
|
||||
if [ "5.6" == ${{ matrix.php }} ] || [ "7.1" == ${{ matrix.php }} ] || [ "7.2" == ${{ matrix.php }} ] || [ "7.3" == ${{ matrix.php }} ]; then
|
||||
PHP_UNIT_ARGS="--exclude-group output"
|
||||
else
|
||||
PHP_UNIT_ARGS=""
|
||||
fi
|
||||
- name: Update APT Sources
|
||||
if: steps.apt-sources-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
mkdir -p /tmp/apt-sources-${{ steps.cache-date.outputs.DATE }}
|
||||
sudo cp -R /var/lib/apt/lists/* /tmp/apt-sources-${{ steps.cache-date.outputs.DATE }}
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit $PHP_UNIT_ARGS --group slow
|
||||
- name: Download APT Packages
|
||||
if: steps.apt-packages-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
sudo cp -R /tmp/apt-sources-${{ steps.cache-date.outputs.DATE }}/* /var/lib/apt/lists
|
||||
sudo apt-get install -y --download-only subversion mysql-client
|
||||
sudo mkdir -p /tmp/apt-packages-${{ steps.cache-date.outputs.DATE }}
|
||||
sudo cp -R /var/cache/apt/archives/*.deb /tmp/apt-packages-${{ steps.cache-date.outputs.DATE }}
|
||||
|
||||
- name: Run PHPUnit Loader Tests
|
||||
run: |
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-loader.xml.dist --filter FontAwesomeLoaderTestLifecycle
|
||||
- name: Install OS Packages
|
||||
run: sudo dpkg -i /tmp/apt-packages-${{ steps.cache-date.outputs.DATE }}/*.deb
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-loader.xml.dist --filter FontAwesomeLoaderTestRedundantVersions
|
||||
- name: Verify DB
|
||||
run: mysql --user=root --password=${MYSQL_ROOT_PASSWORD} --host=${MYSQL_HOST} --port=${MYSQL_PORT} --protocol=tcp -e 'SHOW DATABASES;'
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-loader.xml.dist --filter FontAwesomeLoaderTestBasic
|
||||
- name: Resolve WordPress Version
|
||||
run: |
|
||||
curl -s https://api.wordpress.org/core/version-check/1.7/ > /tmp/wp-latest.json
|
||||
LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//' | head -1)
|
||||
if [ "${{ matrix.wordpress }}" == 'latest' ]; then
|
||||
VERSION=$LATEST_VERSION
|
||||
echo "WORDPRESS_VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_VERSION_IS_TRUNK=0" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_CORE_DIR=/tmp/$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WP_TESTS_TAG=branches/$VERSION" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ matrix.wordpress }}" == 'trunk' ]; then
|
||||
VERSION=trunk-$(date +'%Y-%m-%d')
|
||||
echo "WORDPRESS_VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_VERSION_IS_TRUNK=1" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_CORE_DIR=/tmp/$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WP_TESTS_TAG=trunk" >> $GITHUB_OUTPUT
|
||||
else
|
||||
VERSION=${{ matrix.wordpress }}
|
||||
echo "WORDPRESS_VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_VERSION_IS_TRUNK=0" >> $GITHUB_OUTPUT
|
||||
echo "WORDPRESS_CORE_DIR=/tmp/$VERSION" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run PHPUnit Multisite Tests
|
||||
run: |
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-multisite.xml.dist
|
||||
if [[ $VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
|
||||
WP_BRANCH=${VERSION%\-*}
|
||||
echo "WP_TESTS_TAG=branches/$WP_BRANCH" >> $GITHUB_OUTPUT
|
||||
elif [[ $VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "WP_TESTS_TAG=branches/$VERSION" >> $GITHUB_OUTPUT
|
||||
elif [[ $VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||||
if [[ $VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
|
||||
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
|
||||
echo "WP_TESTS_TAG=tags/${VERSION%??}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "WP_TESTS_TAG=tags/$VERSION" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
elif [[ $VERSION == 'nightly' || $VERSION == 'trunk' ]]; then
|
||||
echo "WP_TESTS_TAG=trunk" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "WP_TESTS_TAG=tags/$LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "WP_TESTS_DIR=/tmp/test/$VERSION" >> $GITHUB_OUTPUT
|
||||
id: wordpress-version
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-multisite-network-admin.xml.dist
|
||||
- name: Show WordPress Version
|
||||
run: echo "The current WordPress version is ${{ steps.wordpress-version.outputs.WORDPRESS_VERSION }}, and WP_TESTS_TAG=${{ steps.wordpress-version.outputs.WP_TESTS_TAG }}"
|
||||
|
||||
- name: Run PHPUnit Multisite SLOW Tests
|
||||
run: |
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-multisite.xml.dist --group slow
|
||||
- name: Cache WordPress Core Installation
|
||||
id: wordpress-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}
|
||||
key: ${{ runner.os }}-wordpress-${{ steps.wordpress-version.outputs.WORDPRESS_VERSION }}') }}
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-multisite-network-admin.xml.dist --group slow
|
||||
- name: Install WordPress Core
|
||||
if: steps.wordpress-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}
|
||||
if [ "${{ steps.wordpress-version.outputs.WORDPRESS_VERSION_IS_TRUNK }}" == "1" ]; then
|
||||
mkdir -p /tmp/wordpress-nightly
|
||||
curl https://wordpress.org/nightly-builds/wordpress-latest.zip > /tmp/wordpress-nightly.zip
|
||||
unzip -q /tmp/wordpress-nightly.zip -d /tmp/wordpress-nightly/
|
||||
mv /tmp/wordpress-nightly/wordpress/* ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}
|
||||
else
|
||||
curl -s https://wordpress.org/wordpress-${{ steps.wordpress-version.outputs.WORDPRESS_VERSION }}.tar.gz > /tmp/wordpress.tar.gz
|
||||
tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}
|
||||
fi
|
||||
curl https://raw.github.com/markoheijnen/wp-mysqli/master/db.php > ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }}/wp-content/db.php
|
||||
|
||||
- name: Maybe run phpcs
|
||||
run: |
|
||||
if [ ${{ matrix.php }} == '7.4' ] && [ ${{ matrix.wordpress }} == latest ]; then
|
||||
composer phpcs
|
||||
echo
|
||||
echo "Skipping phpcs"
|
||||
fi
|
||||
- name: Cache WordPress Test Installation
|
||||
id: wordpress-test-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.wordpress-version.outputs.WP_TESTS_DIR }}
|
||||
key: ${{ runner.os }}-wordpress-test-${{ steps.wordpress-version.outputs.WORDPRESS_VERSION }}') }}
|
||||
|
||||
- name: Install WordPress Test
|
||||
if: steps.wordpress-test-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p ${{ steps.wordpress-version.outputs.WP_TESTS_DIR }}
|
||||
DB_USER=${MYSQL_USER}
|
||||
DB_PASS=${MYSQL_PASSWORD}
|
||||
DB_NAME=${MYSQL_DATABASE}
|
||||
DB_HOST=${MYSQL_HOST}:${MYSQL_PORT}
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }}
|
||||
svn co --quiet https://develop.svn.wordpress.org/${{ steps.wordpress-version.outputs.WP_TESTS_TAG }}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
|
||||
svn co --quiet https://develop.svn.wordpress.org/${{ steps.wordpress-version.outputs.WP_TESTS_TAG }}/tests/phpunit/data/ $WP_TESTS_DIR/data
|
||||
curl https://develop.svn.wordpress.org/${{ steps.wordpress-version.outputs.WP_TESTS_TAG }}/wp-tests-config-sample.php > $WP_TESTS_DIR/wp-tests-config.php
|
||||
# remove all forward slashes in the end
|
||||
WP_CORE_DIR=$(echo ${{ steps.wordpress-version.outputs.WORDPRESS_CORE_DIR }} | sed "s:/\+$::")
|
||||
sed -i "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed -i "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed -i "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed -i "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed -i "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
|
||||
- name: Run PHPUnit Main Tests
|
||||
# only run the output tests on the newer versions of php and phpunit, cause
|
||||
# they're trickier
|
||||
run: |
|
||||
if [ "8.3" == ${{ matrix.php }} ]; then
|
||||
PHP_UNIT_ARGS=""
|
||||
else
|
||||
PHP_UNIT_ARGS="--exclude-group output"
|
||||
fi
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit $PHP_UNIT_ARGS --group slow
|
||||
|
||||
- name: Run PHPUnit Loader Tests
|
||||
# 2/13/25: temporarily disabled. Passing in local dev against WP 6.7.2, failing in CI.
|
||||
if: false
|
||||
run: |
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-loader.xml.dist --filter FontAwesomeLoaderTestLifecycle
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-loader.xml.dist --filter FontAwesomeLoaderTestRedundantVersions
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-loader.xml.dist --filter FontAwesomeLoaderTestBasic
|
||||
|
||||
- name: Run PHPUnit Multisite Tests
|
||||
run: |
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-multisite.xml.dist
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-multisite-network-admin.xml.dist
|
||||
|
||||
- name: Run PHPUnit Multisite SLOW Tests
|
||||
run: |
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-multisite.xml.dist --group slow
|
||||
|
||||
WP_PLUGIN_DIR="$(pwd)" \
|
||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||
WP_TESTS_DIR=${{ steps.wordpress-version.outputs.WP_TESTS_DIR }} \
|
||||
vendor/bin/phpunit --config phpunit-multisite-network-admin.xml.dist --group slow
|
||||
|
||||
- name: Maybe run phpcs
|
||||
run: |
|
||||
if [ ${{ matrix.php }} == '8.2' ] && [ ${{ matrix.wordpress }} == latest ]; then
|
||||
composer phpcs
|
||||
echo
|
||||
echo "Skipping phpcs"
|
||||
fi
|
||||
|
|
|
@ -17,3 +17,10 @@ tmp/
|
|||
webpack-stats.html
|
||||
webpack-stats.json
|
||||
.phpunit.result.cache
|
||||
admin/src/playwright/.auth/
|
||||
admin/artifacts/
|
||||
admin/test-results/
|
||||
.npmrc
|
||||
.php-cs-fixer.cache
|
||||
.zed/
|
||||
.phpactor.json
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"$schema": "/phpactor.schema.json",
|
||||
"language_server": {
|
||||
"diagnostic_exclude_paths": ["**/wp-dist", "**/integrations/**/vendor"]
|
||||
},
|
||||
"indexer": {
|
||||
"exclude_patterns": ["**/wp-dist", "**/integrations/**/vendor"]
|
||||
},
|
||||
"php_code_sniffer.enabled": false,
|
||||
"php_code_sniffer.args": ["--standard=.phpcs.xml"]
|
||||
}
|
|
@ -16,10 +16,13 @@
|
|||
<arg name="extensions" value="php"/>
|
||||
<file>includes</file>
|
||||
<file>tests</file>
|
||||
<!-- TODO: maybe re-enable this. It's crashing phpcbf. -->
|
||||
<!--
|
||||
<file>font-awesome.php</file>
|
||||
-->
|
||||
<file>font-awesome-init.php</file>
|
||||
<file>block-editor/font-awesome-icon-block-init.php</file>
|
||||
<file>index.php</file>
|
||||
<file>v3shims.php</file>
|
||||
<file>defines.php</file>
|
||||
<file>admin/index.php</file>
|
||||
<file>admin/views/main.php</file>
|
|
@ -0,0 +1,6 @@
|
|||
block-editor/build
|
||||
icon-chooser/build
|
||||
classic-editor/build
|
||||
admin/build
|
||||
compat-js/build
|
||||
docs/
|
203
DEVELOPMENT.md
203
DEVELOPMENT.md
|
@ -9,6 +9,7 @@
|
|||
- [Optional Development Setup Steps](#optional-development-setup-steps)
|
||||
- [Run tests with phpunit](#run-tests-with-phpunit)
|
||||
* [Pass arguments to phpunit](#pass-arguments-to-phpunit)
|
||||
- [Run end-to-end tests with playwright](#run-end-to-end-tests-with-playwright)
|
||||
- [Use wp-cli within your Docker environment](#use-wp-cli-within-your-docker-environment)
|
||||
- [Run anything else within your Docker environment](#run-anything-else-within-your-docker-environment)
|
||||
* [Run a shell insider your Docker environment](#run-a-shell-inside-your-docker-environment)
|
||||
|
@ -18,12 +19,14 @@
|
|||
* [Main Options](#main-options)
|
||||
* [Releases Metadata Transient](#releases-metadata-transient)
|
||||
* [V3 Deprecation Warning](#v3-deprecation-warning)
|
||||
- [Managing web security rules](#managing-web-security-rules)
|
||||
- [Cut a Release](#cut-a-release)
|
||||
- [Run a Local Docs Server](#run-a-local-docs-server)
|
||||
- [Special Notes on plugin-sigma](#special-notes-on-plugin-sigma)
|
||||
- [Remote Debugging with VSCode](#remote-debugging-with-vscode)
|
||||
- [Redis Cache Setup](#redis-cache-setup)
|
||||
- [Analyze Webpack Bundle](#analyze-webpack-bundle)
|
||||
- [Test Against a WordPress Release Candidate](#test-against-a-wordpress-release-candidate)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
|
@ -38,7 +41,7 @@ to install any development dependencies inside the container.
|
|||
|
||||
The `integration` option does _not_ mount this plugin code in the container. Instead, it expects you
|
||||
to install the plugin. You could do so by uploading a zip file on the Add New Plugin page in
|
||||
WordPress admin: build a zip using `composer dist` or by downloading one from the [plugin's WordPress
|
||||
WordPress admin: build a zip using the release steps below or by downloading one from the [plugin's WordPress
|
||||
plugin directory entry](https://wordpress.org/plugins/font-awesome/). Or you could install directly
|
||||
from the WordPress plugin directory by searching for plugins by author "fontawesome".
|
||||
|
||||
|
@ -453,27 +456,6 @@ brew install composer
|
|||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>The WordPress 4 compat bundle</summary>
|
||||
For older versions of WordPress, we build and load this separate "compat-js" bundle. It includes some WordPress dependencies that are available at runtime on newer versions of WordPress, but which this plugin provides itself when it detects that
|
||||
it's being loaded on older versions of WordPress.
|
||||
|
||||
This bundle will probably not change very much, so it may not be necessary to rebuild at all.
|
||||
If you're doing development work only in WordPress 5, you can skip this altogether.
|
||||
|
||||
If you do need to update what's in this bundle, though, then you just build another
|
||||
static production build like this:
|
||||
|
||||
```
|
||||
$ cd compat-js
|
||||
$ npm install
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
This will create `compat-js/build/compat.js`, which the plugin looks for and
|
||||
enqueues automatically when it detects that it's running under WordPress 4.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>If you have an older version of Docker or one that doesn't support host.docker.internal</summary>
|
||||
|
||||
|
@ -604,10 +586,57 @@ When specifying a container with `-c`, to add additional command-line arguments,
|
|||
bin/phpunit -c com.fontawesome.wordpress-php7.1-dev -- --filter EnqueueTest
|
||||
```
|
||||
|
||||
Everything before the `--` are the options do the `bin/phpunit` script, and everything after the `--` are what get passed through
|
||||
Everything before the `--` are the options do the `bin/phpunit` script, and everything after the `--` are what get passed through
|
||||
to the `phpunit` command inside the container.
|
||||
</details>
|
||||
|
||||
# Run end-to-end tests with playwright
|
||||
|
||||
## Add tokens to `.env.local`
|
||||
|
||||
```
|
||||
API_TOKEN=YOUR_FA_API_TOKEN
|
||||
KIT_TOKEN=YOUR_KIT_TOKEN
|
||||
```
|
||||
|
||||
To run the end-to-end tests, you must have the WordPress environment running.
|
||||
For example, from the top-level directory, run this:
|
||||
|
||||
```bash
|
||||
bin/dev
|
||||
```
|
||||
|
||||
Leave that running in one terminal and do the following in a separate terminal.
|
||||
|
||||
Playwright must be also installed when initializing a local dev environment:
|
||||
```bash
|
||||
cd admin
|
||||
npx playwright install --with-deps
|
||||
```
|
||||
|
||||
Then, still in the `admin` directory, run tests on the terminal:
|
||||
```bash
|
||||
npx playwright test
|
||||
```
|
||||
|
||||
Or run the tests in the Playwright UI:
|
||||
```bash
|
||||
npx playwright test --ui
|
||||
```
|
||||
|
||||
Or in debug mode:
|
||||
```bash
|
||||
npx playwright test --debug
|
||||
```
|
||||
|
||||
See also [Playwright docs](https://playwright.dev/docs/intro).
|
||||
|
||||
## WordPress Version Caveat
|
||||
|
||||
The end-to-end tests may use features of WordPress that are not present in older versions, so their
|
||||
use on older versions may be limited. But within those limits, at least some of them are useful for
|
||||
running against older versions of WordPress to ensure compatibility.
|
||||
|
||||
# Use WP-CLI within your Docker environment
|
||||
|
||||
For example,
|
||||
|
@ -730,6 +759,36 @@ Remove it:
|
|||
$ bin/wp transient delete font-awesome-v3-deprecation-data
|
||||
```
|
||||
|
||||
# Managing web security rules
|
||||
|
||||
For the `latest` docker image, the latest release of the [OWASP core ruleset](https://coreruleset.org/) is installed by default,
|
||||
but _not_ enabled by default. This simulates what are probably common Web Application Firewall configurations for WordPress hosting providers.
|
||||
|
||||
By default, it merely audits. See the log in `/var/log/apache2/modsec_audit.log`.
|
||||
|
||||
To enable filtering--actually rejecting requests that exceed the rules' tolerances--edit your `.env.local`:
|
||||
|
||||
```
|
||||
ENABLE_MOD_SECURITY=true
|
||||
```
|
||||
|
||||
Note that this env var setting must be present in the environment when the docker container is created.
|
||||
So if you've already started a container, you'll need to stop and remove it, then change this env var,
|
||||
then start it back up.
|
||||
|
||||
You can watch the terminal where `apache2` is launched in the container. When `mod_security` is not enabled,
|
||||
it'll look like this:
|
||||
|
||||
```
|
||||
'apache2 -D FOREGROUND -D DEVELOPMENT'
|
||||
```
|
||||
|
||||
When `mod_security` is enabled, it'll look like this:
|
||||
|
||||
```
|
||||
'apache2 -D FOREGROUND -D DEVELOPMENT -D EnableModSecurity'
|
||||
```
|
||||
|
||||
# Cut a Release
|
||||
|
||||
## Running composer commands for the release
|
||||
|
@ -740,7 +799,7 @@ $ bin/wp transient delete font-awesome-v3-deprecation-data
|
|||
|
||||
3. Update the plugin version const in `includes/class-fontawesome.php`
|
||||
|
||||
4. Update the versions in `admin/package.json` and `compat-js/package.json`
|
||||
4. Update the versions in `admin/package.json`, `block-editor/package.json`, `classic-editor/package.json`, `icon-chooser/package.json`
|
||||
|
||||
5. Wait on changing the "Stable Tag" in `readme.txt` until after we've made the changes in the `svn` repo below.
|
||||
|
||||
|
@ -765,7 +824,7 @@ $ bin/wp transient delete font-awesome-v3-deprecation-data
|
|||
|
||||
- `git add docs` to stage them for commit (and eventually commit them)
|
||||
|
||||
7. Build production admin app and WordPress distribution layout into `wp-dist`
|
||||
7. Build production distribution archive.
|
||||
|
||||
```bash
|
||||
bin/composer dist
|
||||
|
@ -776,20 +835,7 @@ the default dev `latest` container, which you should be running via `bin/dev`.
|
|||
This will cause everything to be built inside the container, which will hopefully
|
||||
keep the built assets more consistent, regardless of the host environment.)
|
||||
|
||||
This will delete the previous build assets and produce the following:
|
||||
|
||||
`admin/build`: production build of the admin UI React app. This needs to be committed, so that it
|
||||
can be included in the composer package (which is really just a pull of this repo)
|
||||
|
||||
`compat-js/build`: production build of the compatibility JS bundled. This also needs to be committed.
|
||||
|
||||
8. Build the zip file
|
||||
|
||||
```bash
|
||||
bin/make-wp-dist-zip
|
||||
```
|
||||
|
||||
This builds the following:
|
||||
This will delete the previous build assets and produce:
|
||||
|
||||
`wp-dist/`: the contents of this directory contains everything that will be used in
|
||||
subsequent steps to both build an installable zip file, and to copy into the
|
||||
|
@ -804,13 +850,7 @@ a GitHub release.
|
|||
|
||||
9. Run through some manual acceptance testing
|
||||
|
||||
**WordPress 4.7, 4.8, 4.9**
|
||||
|
||||
For each of these 4.x environments, run and setup the corresponding integration container, like:
|
||||
```
|
||||
bin/integration 4.7
|
||||
bin/setup -c com.fontawesome.wordpress-4.7-integration
|
||||
```
|
||||
**WordPress 6.0**
|
||||
|
||||
Install and activate the Font Awesome plugin from the admin dashboard by uploading the `font-awesome.zip` file
|
||||
that was created in the previous step.
|
||||
|
@ -819,31 +859,16 @@ Run through the following, with the JavaScript console open, looking for any war
|
|||
|
||||
1. Load the plugin settings page.
|
||||
1. Change from Web Font to SVG and save.
|
||||
1. Create a new post (which will be in the Classic Editor)
|
||||
1. Install the Classic Editor plugin
|
||||
1. Create a new post with the Classic Editor
|
||||
1. Click the "Add Font Awesome" button
|
||||
1. Search for something, and click to insert an icon from the results
|
||||
|
||||
**WordPress 5.0**
|
||||
|
||||
Setup the integration environment as above, but also do the following editor
|
||||
integration tests intead:
|
||||
|
||||
1. Install the Classic Editor plugin
|
||||
1. Create a post with the Classic Editor
|
||||
1. Click the "Add Font Awesome" media button
|
||||
1. Search for something, and click to insert an icon from the results
|
||||
1. Create a new post, switching to the Gutenberg / Block Editor
|
||||
1. Expect to see a compatibility warning that the Icon Chooser is not enabled,
|
||||
but otherwise expect the Block Editor to function normally
|
||||
|
||||
**WordPress 5.4**
|
||||
|
||||
Setup the integration environment as above. Do the same tests as on 5.0, but also
|
||||
expect the Icon Chooser to be enabled within the Block Editor:
|
||||
|
||||
1. Create a post with the Block Editor
|
||||
1. Activate the Icon Chooser
|
||||
1. Add an icon block to a post
|
||||
1. Search for something, and click to insert an icon from the results
|
||||
1. style the icon
|
||||
1. Add a RichText icon (inline)
|
||||
1. style that too
|
||||
|
||||
**WordPress latest**
|
||||
|
||||
|
@ -1062,7 +1087,7 @@ $ cd ..
|
|||
10. Copy plugin directory assets and wp-dist layout into `wp-svn/trunk`
|
||||
|
||||
```bash
|
||||
bin/dist2trunk
|
||||
bin/dist-to-trunk
|
||||
```
|
||||
|
||||
This script will just `rm *` anything under `wp-svn/trunk/*` and `wp-svn/assets/*` to make sure that if the new dist
|
||||
|
@ -1120,15 +1145,7 @@ password. After the first `svn ci` caches the credentials, you probably won't ne
|
|||
|
||||
[See also tips on using SVN with WordPress Plugins](https://developer.wordpress.org/plugins/wordpress-org/how-to-use-subversion/#editing-existing-files).
|
||||
|
||||
13. OPTIONAL: Test Installation from WordPress Plugins Directory
|
||||
|
||||
Once the trunk has been committed above, this new version is available as the current "Development Version",
|
||||
available for download as a ZIP file from the plugin's [Advanced View](https://wordpress.org/plugins/font-awesome/advanced/). (See the dropdown under the header: "Please select a specific version to download.")
|
||||
|
||||
To try it out, you could start up a clean integration environment from this repo, and then
|
||||
install the plugin using that downloaded ZIP file.
|
||||
|
||||
14. Create the new svn release tag
|
||||
13. Create the new svn release tag
|
||||
|
||||
First, make sure `svn stat` is clean. We want to make sure that the trunk is all committed and clean before we take a
|
||||
snapshot of it for the release tag.
|
||||
|
@ -1139,6 +1156,18 @@ This will snapshot `trunk` as a new release tag. Replace the example tag name wi
|
|||
svn cp trunk tags/42.1.2
|
||||
```
|
||||
|
||||
```bash
|
||||
svn ci -m 'Tag release 42.1.2'
|
||||
```
|
||||
|
||||
14. OPTIONAL: Test Installation from WordPress Plugins Directory
|
||||
|
||||
Once the trunk has been committed above, this new version is available as the current "Development Version",
|
||||
available for download as a ZIP file from the plugin's [Advanced View](https://wordpress.org/plugins/font-awesome/advanced/). (See the dropdown under the header: "Please select a specific version to download.")
|
||||
|
||||
To try it out, you could start up a clean integration environment from this repo, and then
|
||||
install the plugin using that downloaded ZIP file.
|
||||
|
||||
15. Update `Stable tag` and `Tested up to` tags in `readme.txt`
|
||||
|
||||
We've now got three copies of `readme.txt` that should all be updated with new tag values:
|
||||
|
@ -1180,15 +1209,11 @@ If you want to preview the built docs with a web server, first build the docs:
|
|||
bin/phpdoc
|
||||
```
|
||||
|
||||
Then go into the `docsrv` directory and run the doc server:
|
||||
Then go into the `docs` directory and run:
|
||||
```
|
||||
cd docsrv
|
||||
npm install
|
||||
node index.js
|
||||
npx serve
|
||||
```
|
||||
|
||||
Point a web browser at `http://localhost:3000`.
|
||||
|
||||
# Special Notes on plugin-sigma
|
||||
|
||||
`plugin-sigma` demonstrates how a third-party plugin developer could include this Font Awesome plugin as a composer
|
||||
|
@ -1271,7 +1296,7 @@ wp --allow-root core update --version=5.4 /tmp/wordpress-5.4-latest.zip
|
|||
|
||||
# Analyze Webpack Bundle
|
||||
|
||||
The webpack configs for both `admin/` and `compat-js/` include the `BundleAnalyzerPlugin`,
|
||||
The webpack configs for the `admin/` JavaScript bundle includes the `BundleAnalyzerPlugin`,
|
||||
which produces a corresponding `webpack-stats.html` file in the corresponding
|
||||
directory on each build.
|
||||
|
||||
|
@ -1289,3 +1314,13 @@ See [guide here](https://wordpress.org/support/article/create-a-network/)
|
|||
```
|
||||
wp --allow-root eval 'require_once "wp-content/plugins/font-awesome/includes/class-fontawesome-deactivator.php"; use FortAwesome\FontAwesome_Deactivator; define("WP_NETWORK_ADMIN", true); FontAwesome_Deactivator::uninstall();'
|
||||
```
|
||||
|
||||
# Test Against a WordPress Release Candidate
|
||||
|
||||
Probably the easiest is to just use `bin/dev` to run the currently-latest release,
|
||||
and then use `bin/env /bin/bash` to shell into that container and use the WP CLI
|
||||
to update the release.
|
||||
|
||||
For example, when WordPress was at `RC3` for version 6.3, this installed as expected:
|
||||
|
||||
`wp --allow-root core update --version=6.3-RC3`
|
||||
|
|
|
@ -733,14 +733,14 @@ Once the site owner enables Pro, `fa()->pro()` will be `true` and your code can
|
|||
then rely on the presence of Font Awesome Pro for the version indicated by
|
||||
`fa()->version()`.
|
||||
|
||||
(See the [PHP API docs](https://fortawesome.github.io/wordpress-fontawesome/index.html) for how to resolve the symbolic
|
||||
(See the [PHP API docs](https://fortawesome.github.io/wordpress-fontawesome/) for how to resolve the symbolic
|
||||
`"latest"` version as a concrete version like `"5.12.0"`.)
|
||||
|
||||
# Query the Font Awesome GraphQL API
|
||||
|
||||
The Font Awesome [GraphQL API](https://fontawesome.com/docs/apis/graphql/get-started) allows you to query and search icon metadata.
|
||||
|
||||
See also documentation in PHP API on the [`FontAwesome::query()`](https://fortawesome.github.io/wordpress-fontawesome/classes/FortAwesome.FontAwesome.html#method_query) method.
|
||||
See also documentation in PHP API on the [`FontAwesome::query()`](https://fortawesome.github.io/wordpress-fontawesome/) method.
|
||||
|
||||
## public scope queries on api.fontawesome.com
|
||||
|
||||
|
@ -755,7 +755,8 @@ fetch(
|
|||
'https://api.fontawesome.com',
|
||||
{
|
||||
method: 'POST',
|
||||
body: 'query { release(version:"5.12.0") { icons { id } } }'
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: '{"query": "query Icons($ver: String!) { release(version:$ver) { icons { id } } }", "variables": { "ver": "6.x" } }'
|
||||
}
|
||||
)
|
||||
.then(response => response.ok ? response.json() : null)
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"bracketSameLine": false,
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"printWidth": 160,
|
||||
"quoteProps": "consistent",
|
||||
"semi": false,
|
||||
"singleAttributePerLine": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"useTabs": false
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,278 +0,0 @@
|
|||
.Ihb3kyONjVgEWV0zB0vfxQ\=\= {
|
||||
margin-right: 20px;
|
||||
padding: 1rem 2rem 2rem 2rem;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1024px) {
|
||||
.Ihb3kyONjVgEWV0zB0vfxQ\=\= {
|
||||
max-width: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
.ToMOGjAmxSPs6D\+glWGx9A\=\= {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
._3edKMOIt9iftk0Zz-7ia9w\=\= {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
div._3edKMOIt9iftk0Zz-7ia9w\=\=.NbqVQtCU5W3ihwasrYexfA\=\= {
|
||||
display: flex;
|
||||
margin: 1em;
|
||||
background-color: #fda09a;
|
||||
border-radius: 5px;
|
||||
max-width: 450px;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
div._3edKMOIt9iftk0Zz-7ia9w\=\=.NbqVQtCU5W3ihwasrYexfA\=\= .gEl1fl\+2lk74ueiQz5cPxQ\=\= {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
div._3edKMOIt9iftk0Zz-7ia9w\=\=.NbqVQtCU5W3ihwasrYexfA\=\= .vumGDcuTrv0Ekcc\+McKiXw\=\= {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
._3edKMOIt9iftk0Zz-7ia9w\=\= .vumGDcuTrv0Ekcc\+McKiXw\=\= {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
._3edKMOIt9iftk0Zz-7ia9w\=\=._7IGgJmOfwN1O\+c0smUgA9Q\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\= {
|
||||
color: green;
|
||||
}
|
||||
|
||||
h2._3hBTOhCdvQjibQScAbzMIQ\=\= {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h3._3hBTOhCdvQjibQScAbzMIQ\=\= {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
._0XZF4B-SzNg4vm8Dl0F7TA\=\= {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.YGhcFGkqFAeqpY9iNYq9Sw\=\= th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button.mQ\+dGgo7ePYMdFVM7UJy3Q\=\= {
|
||||
border: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.LZqubosol4XlcTmVPXrgwA\=\= {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
._92G6m9T1MVtvbrtbN0ztew\=\= {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
button.vKD-ffoQma2PtYJ6syJLXA\=\= {
|
||||
border: 1px solid #0064B1;
|
||||
border-bottom: 4px solid #0064B1;
|
||||
border-radius: 3px;
|
||||
padding: .7em 1.5em;
|
||||
background: #008DED;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.vKD-ffoQma2PtYJ6syJLXA\=\=[disabled] {
|
||||
border: 1px solid #F8F9FA;
|
||||
background: #F8F9FA;
|
||||
color: #008DED;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
button .jRDHFr0fk9vh0tmPg3yyNA\=\= {
|
||||
display: inline-block;
|
||||
min-width: 3.2em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.BHff-dIr\+7jxh1slKud1UA\=\= {
|
||||
background-color: #fdfdf3;
|
||||
max-width: 600px;
|
||||
padding: 1.5em;
|
||||
border-radius: 5px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
._8sv48aq5xq1UY1HM-IXXWw\=\= {
|
||||
border: 0;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1024px) {
|
||||
.XWyrhxEjrFCimjviedIRKg\=\= {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.KIG-iO8JlK18PTTkxmFFfQ\=\= {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.wbV6cqcB6HeXauGMWUOUJw\=\= {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.PWf16KXgVsL5DasX-69r\+w\=\= {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.BFnd\+XqC\+F5AvKCZ6eMOOA\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\= {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.BFnd\+XqC\+F5AvKCZ6eMOOA\=\=.x86R9\+0TG6mMWrDQqDWMxQ\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\=, ._8fwuockVscy-LVkmd5sRrg\=\= {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.BFnd\+XqC\+F5AvKCZ6eMOOA\=\=._4ywMQ6iToIUtlBzG0klUZQ\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\=, .n2ieOzL8DYqXeYvR4dnlBQ\=\= {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.BFnd\+XqC\+F5AvKCZ6eMOOA\=\=.BHff-dIr\+7jxh1slKud1UA\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\=, .Kc0JjWetOt7bzIP5T5F-3g\=\= {
|
||||
color: #b7b700;
|
||||
}
|
||||
|
||||
.cdItXesO30xESmpowZCWVA\=\= {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.XrgwDjPx-AobEiR9810sug\=\= ~ label .wfGA8rTfLXMNYeed\+7P0mg\=\=, .YGOg\+3jg-Q6uUjZrsKJJBw\=\= ~ label .wfGA8rTfLXMNYeed\+7P0mg\=\= {
|
||||
display:none;
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
.XrgwDjPx-AobEiR9810sug\=\=:checked ~ label .wfGA8rTfLXMNYeed\+7P0mg\=\=, .YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .wfGA8rTfLXMNYeed\+7P0mg\=\={
|
||||
display:block;
|
||||
opacity:1.0;
|
||||
color: #228be6;
|
||||
}
|
||||
|
||||
.XrgwDjPx-AobEiR9810sug\=\=:checked ~ label .dxKTDsQBZGG-O07iRE7TNg\=\=, .YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .dxKTDsQBZGG-O07iRE7TNg\=\={
|
||||
display:none;
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
.YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .AX07i\+p1n9K\+g4HYk3mvOg\=\= {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .AX07i\+p1n9K\+g4HYk3mvOg\=\= a {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .AX07i\+p1n9K\+g4HYk3mvOg\=\= a:hover,
|
||||
.YGOg\+3jg-Q6uUjZrsKJJBw\=\= ~ label .AX07i\+p1n9K\+g4HYk3mvOg\=\= a:hover {
|
||||
color: #228be6;
|
||||
text-decoration-color: initial;
|
||||
}
|
||||
|
||||
|
||||
.d7wuKQTkcJufIbd\+gVhKnw\=\= {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.F6JwARlyXPrf\+kIygaN8dQ\=\= {
|
||||
padding: 1rem;
|
||||
margin: 1rem 1rem 1rem 0rem;
|
||||
border: 1px dotted grey;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.v2APGCcZUAaU68TnPHhvxw\=\= {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: no-wrap;
|
||||
align-items: stretch;
|
||||
background-color: #E4F6FF;
|
||||
color: rgb(73, 80, 87);
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 1rem;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.syPwBWS1kp-zUKz4hcgcXg\=\= {
|
||||
color: #008BED;
|
||||
border-top-left-radius: 0.25rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
padding: .55rem .25rem .5rem .75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.ptjLX6BwJtUff-P6OkZBiA\=\= {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.2rem;
|
||||
font-size: .8rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: .5rem 1rem .75rem .25rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= p {
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= svg {
|
||||
font-size: .7rem;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= li {
|
||||
display: inline-block;
|
||||
padding-right: 1rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.CIIJrcA\+PLxU-W4xIVozXw\=\= {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.v2APGCcZUAaU68TnPHhvxw\=\= button {
|
||||
color: #0073aa;
|
||||
}
|
||||
|
||||
.v2APGCcZUAaU68TnPHhvxw\=\= button:hover {
|
||||
color: #00a0d2;
|
||||
}
|
||||
|
||||
/* type: warning */
|
||||
.iAbTOYj3VuCpNr1NEwmL4g\=\= {
|
||||
background: rgb(255, 249, 219);
|
||||
}
|
||||
|
||||
.iAbTOYj3VuCpNr1NEwmL4g\=\= .syPwBWS1kp-zUKz4hcgcXg\=\= {
|
||||
color: rgb(250, 176, 7);
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -1 +0,0 @@
|
|||
(window.webpackJsonp_font_awesome_admin=window.webpackJsonp_font_awesome_admin||[]).push([[13],{289:function(n,o,w){"use strict";w.r(o)}}]);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
(window.webpackJsonp_font_awesome_admin=window.webpackJsonp_font_awesome_admin||[]).push([[16],{281:function(i,t,s){"use strict";s.r(t),s.d(t,"fa_icon",(function(){return o}));var e=s(184),n=s(206);let o=class{constructor(i){Object(e.j)(this,i),this.pro=!1,this.loading=!1}componentWillLoad(){if(this.iconUpload)return void(this.iconDefinition={prefix:"fak",iconName:this.iconUpload.name,icon:[parseInt(""+this.iconUpload.width),parseInt(""+this.iconUpload.height),[],this.iconUpload.unicode.toString(16),this.iconUpload.path]});if(this.icon)return void(this.iconDefinition=this.icon);if(!this.svgApi)return void console.error(n.a+": fa-icon: svgApi prop is needed but is missing",this);if(!this.stylePrefix||!this.name)return void console.error(n.a+": fa-icon: the 'stylePrefix' and 'name' props are needed to render this icon but not provided.",this);const{findIconDefinition:i}=this.svgApi,t=i&&i({prefix:this.stylePrefix,iconName:this.name});if(t)return void(this.iconDefinition=t);if(!this.pro)return void console.error(n.a+": fa-icon: 'pro' prop is false but no free icon is available",this);if(!this.svgFetchBaseUrl)return void console.error(n.a+": fa-icon: 'svgFetchBaseUrl' prop is absent but is necessary for fetching icon",this);if(!this.kitToken)return void console.error(n.a+": fa-icon: 'kitToken' prop is absent but is necessary for accessing icon",this);this.loading=!0;const s=`${this.svgFetchBaseUrl}/${n.c[this.stylePrefix]}/${this.name}.svg?token=${this.kitToken}`,e=n.k.get(this,"svgApi.library");"function"==typeof this.getUrlText?this.getUrlText(s).then(i=>{const t={iconName:this.name,prefix:this.stylePrefix,icon:Object(n.l)(i)};e&&e.add(t),this.iconDefinition=Object.assign({},t)}).catch(i=>{console.error(n.a+": fa-icon: failed when using 'getUrlText' to fetch icon",i,this)}).finally(()=>{this.loading=!1}):console.error(n.a+": fa-icon: 'getUrlText' prop is absent but is necessary for fetching icon",this)}buildSvg(i,t){if(!i)return;const[s,o,,,r]=n.k.get(i,"icon",[]),c=["svg-inline--fa"];this.class&&c.push(this.class),t&&c.push(t),this.size&&c.push("fa-"+this.size);const a=c.join(" ");return Array.isArray(r)?Object(e.h)("svg",{class:a,xmlns:"http://www.w3.org/2000/svg",viewBox:`0 0 ${s} ${o}`},Object(e.h)("path",{fill:"currentColor",class:"fa-primary",d:r[1]}),Object(e.h)("path",{fill:"currentColor",class:"fa-secondary",d:r[0]})):Object(e.h)("svg",{class:a,xmlns:"http://www.w3.org/2000/svg",viewBox:`0 0 ${s} ${o}`},Object(e.h)("path",{fill:"currentColor",d:r}))}render(){return this.iconDefinition?this.buildSvg(this.iconDefinition):Object(e.h)(e.f,null)}};o.style=""}}]);
|
|
@ -1 +0,0 @@
|
|||
(window.webpackJsonp_font_awesome_admin=window.webpackJsonp_font_awesome_admin||[]).push([[17],{279:function(t,e,s){"use strict";s.r(e),s.d(e,"scopeCss",(function(){return j}));const o=")(?:\\(((?:\\([^)(]*\\)|[^)(]*)+?)\\))?([^,{]*)",r=new RegExp("(-shadowcsshost"+o,"gim"),c=new RegExp("(-shadowcsscontext"+o,"gim"),n=new RegExp("(-shadowcssslotted"+o,"gim"),l=/-shadowcsshost-no-combinator([^\s]*)/,a=[/::shadow/g,/::content/g],i=/-shadowcsshost/gim,h=/:host/gim,p=/::slotted/gim,d=/:host-context/gim,u=/\/\*\s*[\s\S]*?\*\//g,m=/\/\*\s*#\s*source(Mapping)?URL=[\s\S]+?\*\//g,g=/(\s*)([^;\{\}]+?)(\s*)((?:{%BLOCK%}?\s*;?)|(?:\s*;))/g,w=/([{}])/g,f=/(^.*?[^\\])??((:+)(.*)|$)/,_=(t,e)=>{const s=x(t);let o=0;return s.escapedString.replace(g,(...t)=>{const r=t[2];let c="",n=t[4],l="";n&&n.startsWith("{%BLOCK%")&&(c=s.blocks[o++],n=n.substring("%BLOCK%".length+1),l="{");const a=e({selector:r,content:c});return`${t[1]}${a.selector}${t[3]}${l}${a.content}${n}`})},x=t=>{const e=t.split(w),s=[],o=[];let r=0,c=[];for(let t=0;t<e.length;t++){const n=e[t];"}"===n&&r--,r>0?c.push(n):(c.length>0&&(o.push(c.join("")),s.push("%BLOCK%"),c=[]),s.push(n)),"{"===n&&r++}return c.length>0&&(o.push(c.join("")),s.push("%BLOCK%")),{escapedString:s.join(""),blocks:o}},$=(t,e,s)=>t.replace(e,(...t)=>{if(t[2]){const e=t[2].split(","),o=[];for(let r=0;r<e.length;r++){const c=e[r].trim();if(!c)break;o.push(s("-shadowcsshost-no-combinator",c,t[3]))}return o.join(",")}return"-shadowcsshost-no-combinator"+t[3]}),b=(t,e,s)=>t+e.replace("-shadowcsshost","")+s,O=(t,e,s)=>e.indexOf("-shadowcsshost")>-1?b(t,e,s):t+e+s+", "+e+" "+t+s,S=(t,e)=>t.replace(f,(t,s="",o,r="",c="")=>s+e+r+c),W=(t,e,s,o,r)=>_(t,t=>{let r=t.selector,c=t.content;return"@"!==t.selector[0]?r=((t,e,s,o)=>t.split(",").map(t=>o&&t.indexOf("."+o)>-1?t.trim():((t,e)=>!(t=>(t=t.replace(/\[/g,"\\[").replace(/\]/g,"\\]"),new RegExp("^("+t+")([>\\s~+[.,{:][\\s\\S]*)?$","m")))(e).test(t))(t,e)?((t,e,s)=>{const o="."+(e=e.replace(/\[is=([^\]]*)\]/g,(t,...e)=>e[0])),r=t=>{let r=t.trim();if(!r)return"";if(t.indexOf("-shadowcsshost-no-combinator")>-1)r=((t,e,s)=>{if(i.lastIndex=0,i.test(t)){const e="."+s;return t.replace(l,(t,s)=>S(s,e)).replace(i,e+" ")}return e+" "+t})(t,e,s);else{const e=t.replace(i,"");e.length>0&&(r=S(e,o))}return r},c=(t=>{const e=[];let s,o=0;return s=(t=t.replace(/(\[[^\]]*\])/g,(t,s)=>{const r=`__ph-${o}__`;return e.push(s),o++,r})).replace(/(:nth-[-\w]+)(\([^)]+\))/g,(t,s,r)=>{const c=`__ph-${o}__`;return e.push(r),o++,s+c}),{content:s,placeholders:e}})(t);let n,a="",h=0;const p=/( |>|\+|~(?!=))\s*/g;let d=!((t=c.content).indexOf("-shadowcsshost-no-combinator")>-1);for(;null!==(n=p.exec(t));){const e=n[1],s=t.slice(h,n.index).trim();d=d||s.indexOf("-shadowcsshost-no-combinator")>-1,a+=`${d?r(s):s} ${e} `,h=p.lastIndex}const u=t.substring(h);return d=d||u.indexOf("-shadowcsshost-no-combinator")>-1,a+=d?r(u):u,m=c.placeholders,a.replace(/__ph-(\d+)__/g,(t,e)=>m[+e]);var m})(t,e,s).trim():t.trim()).join(", "))(t.selector,e,s,o):(t.selector.startsWith("@media")||t.selector.startsWith("@supports")||t.selector.startsWith("@page")||t.selector.startsWith("@document"))&&(c=W(t.content,e,s,o)),{selector:r.replace(/\s{2,}/g," ").trim(),content:c}}),j=(t,e,s)=>{const o=e+"-h",l=e+"-s",i=t.match(m)||[];t=(t=>t.replace(u,""))(t);const g=[];if(s){const e=t=>{const e=`/*!@___${g.length}___*/`,s=`/*!@${t.selector}*/`;return g.push({placeholder:e,comment:s}),t.selector=e+t.selector,t};t=_(t,t=>"@"!==t.selector[0]?e(t):t.selector.startsWith("@media")||t.selector.startsWith("@supports")||t.selector.startsWith("@page")||t.selector.startsWith("@document")?(t.content=_(t.content,e),t):t)}const w=((t,e,s,o,l)=>{const i=((t,e)=>{const s="."+e+" > ",o=[];return t=t.replace(n,(...t)=>{if(t[2]){const e=t[2].trim(),r=t[3],c=s+e+r;let n="";for(let e=t[4]-1;e>=0;e--){const s=t[5][e];if("}"===s||","===s)break;n=s+n}const l=n+c,a=`${n.trimRight()}${c.trim()}`;if(l.trim()!==a.trim()){const t=`${a}, ${l}`;o.push({orgSelector:l,updatedSelector:t})}return c}return"-shadowcsshost-no-combinator"+t[3]}),{selectors:o,cssText:t}})(t=(t=>$(t,c,O))(t=(t=>$(t,r,b))(t=t.replace(d,"-shadowcsscontext").replace(h,"-shadowcsshost").replace(p,"-shadowcssslotted"))),o);return t=(t=>a.reduce((t,e)=>t.replace(e," "),t))(t=i.cssText),e&&(t=W(t,e,s,o)),{cssText:(t=(t=t.replace(/-shadowcsshost-no-combinator/g,"."+s)).replace(/>\s*\*\s+([^{, ]+)/gm," $1 ")).trim(),slottedSelectors:i.selectors}})(t,e,o,l);return t=[w.cssText,...i].join("\n"),s&&g.forEach(({placeholder:e,comment:s})=>{t=t.replace(e,s)}),w.slottedSelectors.forEach(e=>{t=t.replace(e.orgSelector,e.updatedSelector)}),t}}}]);
|
|
@ -1 +0,0 @@
|
|||
(window.webpackJsonp_font_awesome_admin=window.webpackJsonp_font_awesome_admin||[]).push([[18],{285:function(e,o,n){"use strict";n.r(o);var s=n(170),t=n.n(s);o.default=e=>t.a.get(e).then(e=>e.status>=200||e.satus<=299?e.data:(console.error(e),Promise.reject("Font Awesome plugin unexpected response for Icon Chooser"))).catch(e=>(console.error(e),Promise.reject(e)))}}]);
|
|
@ -1 +0,0 @@
|
|||
(window.webpackJsonp_font_awesome_admin=window.webpackJsonp_font_awesome_admin||[]).push([[19],{284:function(e,a,o){"use strict";o.r(a);var r=o(157),t=o.n(r);a.default=e=>async a=>{try{const{apiNonce:o,rootUrl:r,restApiNamespace:n}=e;return t.a.use(t.a.createRootURLMiddleware(r)),t.a.use(t.a.createNonceMiddleware(o)),await t()({path:n+"/api",method:"POST",body:a})}catch(e){throw console.error("CAUGHT:",e),new Error(e)}}}}]);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,10 @@
|
|||
.iVV55iNB320NJJLspy7m{max-width:600px}.BcmdF5mOoQ3Luug6sJrn{border:1px dotted grey;border-radius:5px;margin:1rem 1rem 1rem 0;padding:1rem}
|
||||
.lX8h3LbX6kaLN7_hLhlw{align-items:stretch;background-color:#e4f6ff;border-radius:.25rem;color:#495057;display:flex;flex-direction:row;flex-wrap:no-wrap;margin-top:1rem;max-width:800px}.nx2ZqeD9AnYnPnKHAqKJ{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem;color:#008bed;font-size:1rem;padding:.55rem .25rem .5rem .75rem}.ovRzytWn5jGccLKV78T9{font-size:.8rem;font-weight:600;line-height:1.5;margin-bottom:.2rem;margin-top:0}.M_C6Dj_EqhO8IuY52iA6{display:flex;flex-direction:column;flex-grow:1;font-size:.8rem;padding:.5rem 1rem .75rem .25rem}.M_C6Dj_EqhO8IuY52iA6 p{margin-bottom:.5rem;margin-top:0}.M_C6Dj_EqhO8IuY52iA6 svg{font-size:.7rem}.M_C6Dj_EqhO8IuY52iA6 ul{margin:0}.M_C6Dj_EqhO8IuY52iA6 li{display:inline-block;margin-bottom:0;padding-right:1rem}.liWjpcvKZkKaYPsJjQPA{margin-top:1rem}.lX8h3LbX6kaLN7_hLhlw button{color:#0073aa}.lX8h3LbX6kaLN7_hLhlw button:hover{color:#00a0d2}.MLwfZfK5uVZOtIHI1cdt{background:#fff9db}.MLwfZfK5uVZOtIHI1cdt .nx2ZqeD9AnYnPnKHAqKJ{color:#fab007}
|
||||
.uL1wb8HtJb_0IkG3PeEN{max-width:100%}.VJ5yoahvOPmye_Iv2x78 button{background:#0000;border:none;font-size:1.5em;margin-left:.125em;margin-right:.125em;padding-bottom:.25em}.VJ5yoahvOPmye_Iv2x78 button:hover{cursor:pointer}.VJ5yoahvOPmye_Iv2x78 button:disabled{border-bottom:4px solid #008ded;color:unset;cursor:default}
|
||||
.GfbntzmAC3JXYwdY57Hz{color:red}.nkCRdVrm4cTGk23nWmwZ{display:flex;width:100%}.nkCRdVrm4cTGk23nWmwZ svg{margin-bottom:.125em;margin-right:.5em}._V54L7D6KbeTsNGQHyuu{font-size:.9rem;font-weight:700;width:30%}.jH1jbDhGqqv7Grbj6p5p{margin-right:.25rem;text-align:center;width:1rem}.KYI6mFOoLc2LXE3_WOAV{display:flex;flex-direction:row}.uMUQXnIQhpFojiorojGR{width:50%}.Q3PqTXZI2xQbQuon9Brf{display:flex;flex-direction:column;margin-top:.5rem;width:70%}@media only screen and (min-width:1024px){.Q3PqTXZI2xQbQuon9Brf{margin-top:0}}.p0FxJ9gY4EvqdrEJ5pDs{font-style:italic}.p0FxJ9gY4EvqdrEJ5pDs>ul{list-style:none;margin-left:2em}.rr6y9ViQbhSy0YAp3Svc{display:flex;flex-direction:flex-row}.d104q4pTcfNp1fl3EEwj{font-weight:600;margin-left:5px}.pFNsNzsa6QtxNtsHMsos{margin-bottom:1rem;margin-top:1rem}.x21UupVYDcbhJEXG_vuB{margin-top:1rem}.x21UupVYDcbhJEXG_vuB button{background:none;border:none;cursor:pointer;text-decoration:underline}.x21UupVYDcbhJEXG_vuB button svg{margin-right:.5em}.elGAHNDAzrtnF0LZNYl_ .uMUQXnIQhpFojiorojGR{margin-bottom:1.5rem}.FHOPD8z6_efjfjjQHK9B{color:#868e96;display:block;font-weight:400;line-height:1.5;margin-top:.25rem}.FHOPD8z6_efjfjjQHK9B a{color:#868e96}.FHOPD8z6_efjfjjQHK9B a:hover{color:#228be6}.kP55KzmQ_7zoJ5baS76k{padding-left:1em}
|
||||
.xAYNgmh_FT28wOZEe4og{background-color:#fff;margin-right:20px;padding:1rem 2rem 2rem}@media only screen and (min-width:1024px){.xAYNgmh_FT28wOZEe4og{max-width:1000px}}.W3wz4Liah2EvWxtTBXN8{align-items:center;display:flex}.OpLLWfmNs6BXGmnmuinK{padding-left:1em}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt{background-color:#fda09a;border-radius:5px;display:flex;margin:1em;max-width:450px;padding:1em}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt .A2dLn8oZtVzuXngZMDRp{margin-bottom:auto;margin-top:auto}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt .xFoMk9Jc8Ir4n5Olcce1{max-width:400px}.OpLLWfmNs6BXGmnmuinK .xFoMk9Jc8Ir4n5Olcce1{padding-left:1em}.OpLLWfmNs6BXGmnmuinK.fQeEY3YNz4yh6R7vdi7J .JPBgwk6PxfiitLxJLE54{color:green}h2.VklefjWwawC59yrOPe3e{font-size:18px}h3.VklefjWwawC59yrOPe3e{font-size:16px}.e8Vu3y2YBkuW8N9IhY2m{margin-bottom:1rem;margin-top:1rem}.gNYVG50hxMZs8Gqbj_T0 th{font-weight:700}button.dpYyb_l0GWlAiVkOmmYt{background:none;border:0}.WJl_9YHKGkhUvtVwgVco{align-items:center;display:flex}.HBCEbIhIET1XISEYneSA{margin:1rem}button.ZXe2iyFqFThwx_UF4CBf{background:#008ded;border:solid #0064b1;border-radius:3px;border-width:1px 1px 4px;color:#fff;cursor:pointer;font-size:14px;font-weight:600;line-height:1.4em;padding:.7em 1.5em}button.ZXe2iyFqFThwx_UF4CBf[disabled]{background:#f8f9fa;border:1px solid #f8f9fa;color:#008ded;cursor:default}button .HgLyUkphZYd8YsLSMJAZ{display:inline-block;min-width:3.2em;text-align:left}.Gu2u4ZSZT25Yqm8zSogj{background-color:#fdfdf3;border:1px solid #000;border-radius:5px;max-width:600px;padding:1.5em}.WOV9bdVrpJVdQWzhBnHZ{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}@media only screen and (min-width:1024px){.QN_KH8sqi5QFBDqaH1rI{display:flex}}.bBMVcUUJf1GW7veG1Zic{flex-direction:row}.pIa2BGO1ABMtYZY185Bf{flex-direction:column}.h0koIl1zvME7toM3jUk9{position:relative}.kWqY7l_wn27HmdUNz7ZY .JPBgwk6PxfiitLxJLE54{font-size:24px}.kWqY7l_wn27HmdUNz7ZY.q3No9l7YMUEH1xvYTNfI .JPBgwk6PxfiitLxJLE54,.Y7M4JHzDp7jtCt6MonbK{color:green}.a6qTuZmDiKS_FHgMZawo,.kWqY7l_wn27HmdUNz7ZY.PwCQsIQEdGz9b0cOj3iA .JPBgwk6PxfiitLxJLE54{color:red}.kWqY7l_wn27HmdUNz7ZY.Gu2u4ZSZT25Yqm8zSogj .JPBgwk6PxfiitLxJLE54,.rw5FUVRrrdM17WyxcRZ9{color:#b7b700}.QEoklKhbCbwOUBs0cspa{margin-left:1rem}.oWHnpotXuoOIlJoqkkgw~label .NzRaF0U8aKPVtS6JIaK8,.gIUwcNcpOHhTKG4sTlfg~label .NzRaF0U8aKPVtS6JIaK8{display:none;opacity:0}.oWHnpotXuoOIlJoqkkgw:checked~label .NzRaF0U8aKPVtS6JIaK8,.gIUwcNcpOHhTKG4sTlfg:checked~label .NzRaF0U8aKPVtS6JIaK8{color:#228be6;display:block;opacity:1}.oWHnpotXuoOIlJoqkkgw:checked~label .iemYJRvB4tzF1xnuGiAw,.gIUwcNcpOHhTKG4sTlfg:checked~label .iemYJRvB4tzF1xnuGiAw{display:none;opacity:0}.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS,.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS a{color:#495057}.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS a:hover,.oWHnpotXuoOIlJoqkkgw~label .BFR5diS8tiViycbuTDVS a:hover{color:#228be6;text-decoration-color:initial}
|
||||
.kaJ7ZfPW8LYXB3jcG1xM{position:relative}.iw1ugzHiscI8cdqPxDt5{border-bottom:1px solid #dde2e6;display:flex;padding:1rem 1rem 1rem 0}.iw1ugzHiscI8cdqPxDt5 label{font-size:.9rem;font-weight:600;width:30%}.iw1ugzHiscI8cdqPxDt5 label svg{color:#dde2e6;padding-right:.5rem}.DnA1Iv_lwCTNSAFQGrON .iw1ugzHiscI8cdqPxDt5 p{font-size:unset;font-weight:unset}.DnA1Iv_lwCTNSAFQGrON .Pnf8O2SgfIFmVM0PSv3Z p svg{color:unset;padding-right:.5rem}.DnA1Iv_lwCTNSAFQGrON{border-bottom:1px solid #dde2e6;display:flex;flex-direction:column;margin-bottom:.75rem;padding:.5rem 1rem 1rem 0}.iw1ugzHiscI8cdqPxDt5.kjodIeFA7B16RQcru0GW{border-bottom:none}.Pnf8O2SgfIFmVM0PSv3Z{display:flex;justify-content:space-between}.Pnf8O2SgfIFmVM0PSv3Z.kjodIeFA7B16RQcru0GW{flex-direction:column}.DnA1Iv_lwCTNSAFQGrON p{font-size:.9rem;font-weight:600;margin:0;padding:.5rem 0}.Pnf8O2SgfIFmVM0PSv3Z .A0_oAmpyVJ9wdtADndGQ span svg{color:#00c346;padding-right:.5rem}button.HfzrDbHUd_u1i9ndGEBR{background-color:initial;border:none;border-radius:3px;color:#999;cursor:pointer;display:inline-block;margin-left:-.1em;padding:.5rem 1rem;transition:background .1s ease-in;transition:.1s ease-in}button.HfzrDbHUd_u1i9ndGEBR:hover{background-color:#da001d;color:#fff}.elgzg717O9Crp2uzkrTD button{margin-left:2em}.V9u2jF9aJPfN0wX4PDVS{background-color:none;border:none;cursor:pointer;line-height:2.15384615;margin-left:1rem;text-decoration:underline;text-decoration-color:#00000026}.V9u2jF9aJPfN0wX4PDVS:hover{text-decoration-color:#000}.wziOBkVmZ17vJu2Sz35G{border-bottom:1px solid #dde2e6;margin-bottom:.75rem;padding:.5rem 1rem 1rem 0}.DFhEV9q8j6_YiAqxGCIQ{font-size:.9rem;font-weight:600;margin:0;padding:.5rem 0}.DFhEV9q8j6_YiAqxGCIQ.y9VOhnGapgfPZMY_1Bh6 svg{color:#00c346;padding-right:.5rem}.DFhEV9q8j6_YiAqxGCIQ.czVzoPvuSXhcttrzFc9L svg{color:#f8f9fa}.ngog_nfdj6MCATJVipdj{padding:.5rem 1rem 1rem 0}.S_T55Hlv5ASeRm2CyHmV{font-size:.9rem;font-weight:600;margin:0 0 1rem}.S_T55Hlv5ASeRm2CyHmV svg{color:#dde2e6;padding-right:.5rem}.fy5GsBzkO8Epk2RR04LH{margin-left:1.8rem}button.fNJ_UaCGqOZxk72kjIDA{background-color:initial;border:none;border-radius:3px;color:#228be6;cursor:pointer;display:inline-block;margin:0 0 0 .2rem;padding:.5rem;transition:background .1s ease-in;transition:.1s ease-in;vertical-align:middle}button.fNJ_UaCGqOZxk72kjIDA:hover{background-color:#1c7ed6;color:#fff}button.fNJ_UaCGqOZxk72kjIDA span{padding-left:.5em}.SstVIjmK5UOaiy_ltjXE .vdGJ8TWieYuqqCakSyHG{display:inline-block;font-weight:600;height:auto;margin-right:1rem;padding:.4rem;vertical-align:middle;width:30%}.gJq7WtlHzhBcjrgjABY8{margin-left:1.8rem}.o1IZC2E8wgWEslXswxvX{border-collapse:collapse;font-size:.9rem;margin:0 0 1rem;width:100%}.o1IZC2E8wgWEslXswxvX .W3xNRcvtnX8v6hHiakrQ,.o1IZC2E8wgWEslXswxvX .HS7POK_5ddxGDSTzJHun{border-bottom:1px solid #dde2e6;border-top:1px solid #dde2e6;padding:.5rem;text-align:left;vertical-align:top}.o1IZC2E8wgWEslXswxvX .W3xNRcvtnX8v6hHiakrQ{font-weight:600;width:30%}.co95Sqvd5n7VgX7SM5jt{display:block;font-weight:400;line-height:1.5;margin-top:.25rem}.co95Sqvd5n7VgX7SM5jt,.co95Sqvd5n7VgX7SM5jt a{color:#868e96}.co95Sqvd5n7VgX7SM5jt a:hover{color:#228be6}
|
||||
.wVJC_TuxmtpxI03Tbdkt{border-bottom:2px solid #008ded;display:flex;margin-bottom:.5rem;padding-bottom:1rem}.wVJC_TuxmtpxI03Tbdkt label{margin-right:1rem}
|
||||
|
||||
.FGrSfvJewATz8TfOqA_j th.dDmxKRAWr1lhLPK3Z838,td.dDmxKRAWr1lhLPK3Z838{background-color:#ffe2e2}
|
||||
.heZgRJQYY60l5e4s0W_4 th{vertical-align:top}.heZgRJQYY60l5e4s0W_4 th .cBkIuJWm4fbhOOHopdph{font-weight:700}.heZgRJQYY60l5e4s0W_4 code{font-size:10px}.qxjS23M34RH041PZzC82,.L1uULhjJTYD39y7vA6HC{margin-top:.5rem}.JL6BMdxHE5CPnMDBfHe8{display:flex}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,10 @@
|
|||
.iVV55iNB320NJJLspy7m{max-width:600px}.BcmdF5mOoQ3Luug6sJrn{border:1px dotted grey;border-radius:5px;margin:1rem 0 1rem 1rem;padding:1rem}
|
||||
.lX8h3LbX6kaLN7_hLhlw{align-items:stretch;background-color:#e4f6ff;border-radius:.25rem;color:#495057;display:flex;flex-direction:row;flex-wrap:no-wrap;margin-top:1rem;max-width:800px}.nx2ZqeD9AnYnPnKHAqKJ{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem;color:#008bed;font-size:1rem;padding:.55rem .75rem .5rem .25rem}.ovRzytWn5jGccLKV78T9{font-size:.8rem;font-weight:600;line-height:1.5;margin-bottom:.2rem;margin-top:0}.M_C6Dj_EqhO8IuY52iA6{display:flex;flex-direction:column;flex-grow:1;font-size:.8rem;padding:.5rem .25rem .75rem 1rem}.M_C6Dj_EqhO8IuY52iA6 p{margin-bottom:.5rem;margin-top:0}.M_C6Dj_EqhO8IuY52iA6 svg{font-size:.7rem}.M_C6Dj_EqhO8IuY52iA6 ul{margin:0}.M_C6Dj_EqhO8IuY52iA6 li{display:inline-block;margin-bottom:0;padding-left:1rem}.liWjpcvKZkKaYPsJjQPA{margin-top:1rem}.lX8h3LbX6kaLN7_hLhlw button{color:#0073aa}.lX8h3LbX6kaLN7_hLhlw button:hover{color:#00a0d2}.MLwfZfK5uVZOtIHI1cdt{background:#fff9db}.MLwfZfK5uVZOtIHI1cdt .nx2ZqeD9AnYnPnKHAqKJ{color:#fab007}
|
||||
.uL1wb8HtJb_0IkG3PeEN{max-width:100%}.VJ5yoahvOPmye_Iv2x78 button{background:#0000;border:none;font-size:1.5em;margin-right:.125em;margin-left:.125em;padding-bottom:.25em}.VJ5yoahvOPmye_Iv2x78 button:hover{cursor:pointer}.VJ5yoahvOPmye_Iv2x78 button:disabled{border-bottom:4px solid #008ded;color:unset;cursor:default}
|
||||
.GfbntzmAC3JXYwdY57Hz{color:red}.nkCRdVrm4cTGk23nWmwZ{display:flex;width:100%}.nkCRdVrm4cTGk23nWmwZ svg{margin-bottom:.125em;margin-left:.5em}._V54L7D6KbeTsNGQHyuu{font-size:.9rem;font-weight:700;width:30%}.jH1jbDhGqqv7Grbj6p5p{margin-left:.25rem;text-align:center;width:1rem}.KYI6mFOoLc2LXE3_WOAV{display:flex;flex-direction:row}.uMUQXnIQhpFojiorojGR{width:50%}.Q3PqTXZI2xQbQuon9Brf{display:flex;flex-direction:column;margin-top:.5rem;width:70%}@media only screen and (min-width:1024px){.Q3PqTXZI2xQbQuon9Brf{margin-top:0}}.p0FxJ9gY4EvqdrEJ5pDs{font-style:italic}.p0FxJ9gY4EvqdrEJ5pDs>ul{list-style:none;margin-right:2em}.rr6y9ViQbhSy0YAp3Svc{display:flex;flex-direction:flex-row}.d104q4pTcfNp1fl3EEwj{font-weight:600;margin-right:5px}.pFNsNzsa6QtxNtsHMsos{margin-bottom:1rem;margin-top:1rem}.x21UupVYDcbhJEXG_vuB{margin-top:1rem}.x21UupVYDcbhJEXG_vuB button{background:none;border:none;cursor:pointer;text-decoration:underline}.x21UupVYDcbhJEXG_vuB button svg{margin-left:.5em}.elGAHNDAzrtnF0LZNYl_ .uMUQXnIQhpFojiorojGR{margin-bottom:1.5rem}.FHOPD8z6_efjfjjQHK9B{color:#868e96;display:block;font-weight:400;line-height:1.5;margin-top:.25rem}.FHOPD8z6_efjfjjQHK9B a{color:#868e96}.FHOPD8z6_efjfjjQHK9B a:hover{color:#228be6}.kP55KzmQ_7zoJ5baS76k{padding-right:1em}
|
||||
.xAYNgmh_FT28wOZEe4og{background-color:#fff;margin-left:20px;padding:1rem 2rem 2rem}@media only screen and (min-width:1024px){.xAYNgmh_FT28wOZEe4og{max-width:1000px}}.W3wz4Liah2EvWxtTBXN8{align-items:center;display:flex}.OpLLWfmNs6BXGmnmuinK{padding-right:1em}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt{background-color:#fda09a;border-radius:5px;display:flex;margin:1em;max-width:450px;padding:1em}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt .A2dLn8oZtVzuXngZMDRp{margin-bottom:auto;margin-top:auto}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt .xFoMk9Jc8Ir4n5Olcce1{max-width:400px}.OpLLWfmNs6BXGmnmuinK .xFoMk9Jc8Ir4n5Olcce1{padding-right:1em}.OpLLWfmNs6BXGmnmuinK.fQeEY3YNz4yh6R7vdi7J .JPBgwk6PxfiitLxJLE54{color:green}h2.VklefjWwawC59yrOPe3e{font-size:18px}h3.VklefjWwawC59yrOPe3e{font-size:16px}.e8Vu3y2YBkuW8N9IhY2m{margin-bottom:1rem;margin-top:1rem}.gNYVG50hxMZs8Gqbj_T0 th{font-weight:700}button.dpYyb_l0GWlAiVkOmmYt{background:none;border:0}.WJl_9YHKGkhUvtVwgVco{align-items:center;display:flex}.HBCEbIhIET1XISEYneSA{margin:1rem}button.ZXe2iyFqFThwx_UF4CBf{background:#008ded;border:solid #0064b1;border-radius:3px;border-width:1px 1px 4px;color:#fff;cursor:pointer;font-size:14px;font-weight:600;line-height:1.4em;padding:.7em 1.5em}button.ZXe2iyFqFThwx_UF4CBf[disabled]{background:#f8f9fa;border:1px solid #f8f9fa;color:#008ded;cursor:default}button .HgLyUkphZYd8YsLSMJAZ{display:inline-block;min-width:3.2em;text-align:right}.Gu2u4ZSZT25Yqm8zSogj{background-color:#fdfdf3;border:1px solid #000;border-radius:5px;max-width:600px;padding:1.5em}.WOV9bdVrpJVdQWzhBnHZ{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}@media only screen and (min-width:1024px){.QN_KH8sqi5QFBDqaH1rI{display:flex}}.bBMVcUUJf1GW7veG1Zic{flex-direction:row}.pIa2BGO1ABMtYZY185Bf{flex-direction:column}.h0koIl1zvME7toM3jUk9{position:relative}.kWqY7l_wn27HmdUNz7ZY .JPBgwk6PxfiitLxJLE54{font-size:24px}.kWqY7l_wn27HmdUNz7ZY.q3No9l7YMUEH1xvYTNfI .JPBgwk6PxfiitLxJLE54,.Y7M4JHzDp7jtCt6MonbK{color:green}.a6qTuZmDiKS_FHgMZawo,.kWqY7l_wn27HmdUNz7ZY.PwCQsIQEdGz9b0cOj3iA .JPBgwk6PxfiitLxJLE54{color:red}.kWqY7l_wn27HmdUNz7ZY.Gu2u4ZSZT25Yqm8zSogj .JPBgwk6PxfiitLxJLE54,.rw5FUVRrrdM17WyxcRZ9{color:#b7b700}.QEoklKhbCbwOUBs0cspa{margin-right:1rem}.oWHnpotXuoOIlJoqkkgw~label .NzRaF0U8aKPVtS6JIaK8,.gIUwcNcpOHhTKG4sTlfg~label .NzRaF0U8aKPVtS6JIaK8{display:none;opacity:0}.oWHnpotXuoOIlJoqkkgw:checked~label .NzRaF0U8aKPVtS6JIaK8,.gIUwcNcpOHhTKG4sTlfg:checked~label .NzRaF0U8aKPVtS6JIaK8{color:#228be6;display:block;opacity:1}.oWHnpotXuoOIlJoqkkgw:checked~label .iemYJRvB4tzF1xnuGiAw,.gIUwcNcpOHhTKG4sTlfg:checked~label .iemYJRvB4tzF1xnuGiAw{display:none;opacity:0}.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS,.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS a{color:#495057}.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS a:hover,.oWHnpotXuoOIlJoqkkgw~label .BFR5diS8tiViycbuTDVS a:hover{color:#228be6;text-decoration-color:initial}
|
||||
.kaJ7ZfPW8LYXB3jcG1xM{position:relative}.iw1ugzHiscI8cdqPxDt5{border-bottom:1px solid #dde2e6;display:flex;padding:1rem 0 1rem 1rem}.iw1ugzHiscI8cdqPxDt5 label{font-size:.9rem;font-weight:600;width:30%}.iw1ugzHiscI8cdqPxDt5 label svg{color:#dde2e6;padding-left:.5rem}.DnA1Iv_lwCTNSAFQGrON .iw1ugzHiscI8cdqPxDt5 p{font-size:unset;font-weight:unset}.DnA1Iv_lwCTNSAFQGrON .Pnf8O2SgfIFmVM0PSv3Z p svg{color:unset;padding-left:.5rem}.DnA1Iv_lwCTNSAFQGrON{border-bottom:1px solid #dde2e6;display:flex;flex-direction:column;margin-bottom:.75rem;padding:.5rem 0 1rem 1rem}.iw1ugzHiscI8cdqPxDt5.kjodIeFA7B16RQcru0GW{border-bottom:none}.Pnf8O2SgfIFmVM0PSv3Z{display:flex;justify-content:space-between}.Pnf8O2SgfIFmVM0PSv3Z.kjodIeFA7B16RQcru0GW{flex-direction:column}.DnA1Iv_lwCTNSAFQGrON p{font-size:.9rem;font-weight:600;margin:0;padding:.5rem 0}.Pnf8O2SgfIFmVM0PSv3Z .A0_oAmpyVJ9wdtADndGQ span svg{color:#00c346;padding-left:.5rem}button.HfzrDbHUd_u1i9ndGEBR{background-color:initial;border:none;border-radius:3px;color:#999;cursor:pointer;display:inline-block;margin-right:-.1em;padding:.5rem 1rem;transition:background .1s ease-in;transition:.1s ease-in}button.HfzrDbHUd_u1i9ndGEBR:hover{background-color:#da001d;color:#fff}.elgzg717O9Crp2uzkrTD button{margin-right:2em}.V9u2jF9aJPfN0wX4PDVS{background-color:none;border:none;cursor:pointer;line-height:2.15384615;margin-right:1rem;text-decoration:underline;text-decoration-color:#00000026}.V9u2jF9aJPfN0wX4PDVS:hover{text-decoration-color:#000}.wziOBkVmZ17vJu2Sz35G{border-bottom:1px solid #dde2e6;margin-bottom:.75rem;padding:.5rem 0 1rem 1rem}.DFhEV9q8j6_YiAqxGCIQ{font-size:.9rem;font-weight:600;margin:0;padding:.5rem 0}.DFhEV9q8j6_YiAqxGCIQ.y9VOhnGapgfPZMY_1Bh6 svg{color:#00c346;padding-left:.5rem}.DFhEV9q8j6_YiAqxGCIQ.czVzoPvuSXhcttrzFc9L svg{color:#f8f9fa}.ngog_nfdj6MCATJVipdj{padding:.5rem 0 1rem 1rem}.S_T55Hlv5ASeRm2CyHmV{font-size:.9rem;font-weight:600;margin:0 0 1rem}.S_T55Hlv5ASeRm2CyHmV svg{color:#dde2e6;padding-left:.5rem}.fy5GsBzkO8Epk2RR04LH{margin-right:1.8rem}button.fNJ_UaCGqOZxk72kjIDA{background-color:initial;border:none;border-radius:3px;color:#228be6;cursor:pointer;display:inline-block;margin:0 .2rem 0 0;padding:.5rem;transition:background .1s ease-in;transition:.1s ease-in;vertical-align:middle}button.fNJ_UaCGqOZxk72kjIDA:hover{background-color:#1c7ed6;color:#fff}button.fNJ_UaCGqOZxk72kjIDA span{padding-right:.5em}.SstVIjmK5UOaiy_ltjXE .vdGJ8TWieYuqqCakSyHG{display:inline-block;font-weight:600;height:auto;margin-left:1rem;padding:.4rem;vertical-align:middle;width:30%}.gJq7WtlHzhBcjrgjABY8{margin-right:1.8rem}.o1IZC2E8wgWEslXswxvX{border-collapse:collapse;font-size:.9rem;margin:0 0 1rem;width:100%}.o1IZC2E8wgWEslXswxvX .W3xNRcvtnX8v6hHiakrQ,.o1IZC2E8wgWEslXswxvX .HS7POK_5ddxGDSTzJHun{border-bottom:1px solid #dde2e6;border-top:1px solid #dde2e6;padding:.5rem;text-align:right;vertical-align:top}.o1IZC2E8wgWEslXswxvX .W3xNRcvtnX8v6hHiakrQ{font-weight:600;width:30%}.co95Sqvd5n7VgX7SM5jt{display:block;font-weight:400;line-height:1.5;margin-top:.25rem}.co95Sqvd5n7VgX7SM5jt,.co95Sqvd5n7VgX7SM5jt a{color:#868e96}.co95Sqvd5n7VgX7SM5jt a:hover{color:#228be6}
|
||||
.wVJC_TuxmtpxI03Tbdkt{border-bottom:2px solid #008ded;display:flex;margin-bottom:.5rem;padding-bottom:1rem}.wVJC_TuxmtpxI03Tbdkt label{margin-left:1rem}
|
||||
|
||||
.FGrSfvJewATz8TfOqA_j th.dDmxKRAWr1lhLPK3Z838,td.dDmxKRAWr1lhLPK3Z838{background-color:#ffe2e2}
|
||||
.heZgRJQYY60l5e4s0W_4 th{vertical-align:top}.heZgRJQYY60l5e4s0W_4 th .cBkIuJWm4fbhOOHopdph{font-weight:700}.heZgRJQYY60l5e4s0W_4 code{font-size:10px}.qxjS23M34RH041PZzC82,.L1uULhjJTYD39y7vA6HC{margin-top:.5rem}.JL6BMdxHE5CPnMDBfHe8{display:flex}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,3 @@
|
|||
.xAYNgmh_FT28wOZEe4og{background-color:#fff;margin-right:20px;padding:1rem 2rem 2rem}@media only screen and (min-width:1024px){.xAYNgmh_FT28wOZEe4og{max-width:1000px}}.W3wz4Liah2EvWxtTBXN8{align-items:center;display:flex}.OpLLWfmNs6BXGmnmuinK{padding-left:1em}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt{background-color:#fda09a;border-radius:5px;display:flex;margin:1em;max-width:450px;padding:1em}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt .A2dLn8oZtVzuXngZMDRp{margin-bottom:auto;margin-top:auto}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt .xFoMk9Jc8Ir4n5Olcce1{max-width:400px}.OpLLWfmNs6BXGmnmuinK .xFoMk9Jc8Ir4n5Olcce1{padding-left:1em}.OpLLWfmNs6BXGmnmuinK.fQeEY3YNz4yh6R7vdi7J .JPBgwk6PxfiitLxJLE54{color:green}h2.VklefjWwawC59yrOPe3e{font-size:18px}h3.VklefjWwawC59yrOPe3e{font-size:16px}.e8Vu3y2YBkuW8N9IhY2m{margin-bottom:1rem;margin-top:1rem}.gNYVG50hxMZs8Gqbj_T0 th{font-weight:700}button.dpYyb_l0GWlAiVkOmmYt{background:none;border:0}.WJl_9YHKGkhUvtVwgVco{align-items:center;display:flex}.HBCEbIhIET1XISEYneSA{margin:1rem}button.ZXe2iyFqFThwx_UF4CBf{background:#008ded;border:solid #0064b1;border-radius:3px;border-width:1px 1px 4px;color:#fff;cursor:pointer;font-size:14px;font-weight:600;line-height:1.4em;padding:.7em 1.5em}button.ZXe2iyFqFThwx_UF4CBf[disabled]{background:#f8f9fa;border:1px solid #f8f9fa;color:#008ded;cursor:default}button .HgLyUkphZYd8YsLSMJAZ{display:inline-block;min-width:3.2em;text-align:left}.Gu2u4ZSZT25Yqm8zSogj{background-color:#fdfdf3;border:1px solid #000;border-radius:5px;max-width:600px;padding:1.5em}.WOV9bdVrpJVdQWzhBnHZ{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}@media only screen and (min-width:1024px){.QN_KH8sqi5QFBDqaH1rI{display:flex}}.bBMVcUUJf1GW7veG1Zic{flex-direction:row}.pIa2BGO1ABMtYZY185Bf{flex-direction:column}.h0koIl1zvME7toM3jUk9{position:relative}.kWqY7l_wn27HmdUNz7ZY .JPBgwk6PxfiitLxJLE54{font-size:24px}.kWqY7l_wn27HmdUNz7ZY.q3No9l7YMUEH1xvYTNfI .JPBgwk6PxfiitLxJLE54,.Y7M4JHzDp7jtCt6MonbK{color:green}.a6qTuZmDiKS_FHgMZawo,.kWqY7l_wn27HmdUNz7ZY.PwCQsIQEdGz9b0cOj3iA .JPBgwk6PxfiitLxJLE54{color:red}.kWqY7l_wn27HmdUNz7ZY.Gu2u4ZSZT25Yqm8zSogj .JPBgwk6PxfiitLxJLE54,.rw5FUVRrrdM17WyxcRZ9{color:#b7b700}.QEoklKhbCbwOUBs0cspa{margin-left:1rem}.oWHnpotXuoOIlJoqkkgw~label .NzRaF0U8aKPVtS6JIaK8,.gIUwcNcpOHhTKG4sTlfg~label .NzRaF0U8aKPVtS6JIaK8{display:none;opacity:0}.oWHnpotXuoOIlJoqkkgw:checked~label .NzRaF0U8aKPVtS6JIaK8,.gIUwcNcpOHhTKG4sTlfg:checked~label .NzRaF0U8aKPVtS6JIaK8{color:#228be6;display:block;opacity:1}.oWHnpotXuoOIlJoqkkgw:checked~label .iemYJRvB4tzF1xnuGiAw,.gIUwcNcpOHhTKG4sTlfg:checked~label .iemYJRvB4tzF1xnuGiAw{display:none;opacity:0}.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS,.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS a{color:#495057}.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS a:hover,.oWHnpotXuoOIlJoqkkgw~label .BFR5diS8tiViycbuTDVS a:hover{color:#228be6;text-decoration-color:initial}
|
||||
.iVV55iNB320NJJLspy7m{max-width:600px}.BcmdF5mOoQ3Luug6sJrn{border:1px dotted grey;border-radius:5px;margin:1rem 1rem 1rem 0;padding:1rem}
|
||||
.lX8h3LbX6kaLN7_hLhlw{align-items:stretch;background-color:#e4f6ff;border-radius:.25rem;color:#495057;display:flex;flex-direction:row;flex-wrap:no-wrap;margin-top:1rem;max-width:800px}.nx2ZqeD9AnYnPnKHAqKJ{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem;color:#008bed;font-size:1rem;padding:.55rem .25rem .5rem .75rem}.ovRzytWn5jGccLKV78T9{font-size:.8rem;font-weight:600;line-height:1.5;margin-bottom:.2rem;margin-top:0}.M_C6Dj_EqhO8IuY52iA6{display:flex;flex-direction:column;flex-grow:1;font-size:.8rem;padding:.5rem 1rem .75rem .25rem}.M_C6Dj_EqhO8IuY52iA6 p{margin-bottom:.5rem;margin-top:0}.M_C6Dj_EqhO8IuY52iA6 svg{font-size:.7rem}.M_C6Dj_EqhO8IuY52iA6 ul{margin:0}.M_C6Dj_EqhO8IuY52iA6 li{display:inline-block;margin-bottom:0;padding-right:1rem}.liWjpcvKZkKaYPsJjQPA{margin-top:1rem}.lX8h3LbX6kaLN7_hLhlw button{color:#0073aa}.lX8h3LbX6kaLN7_hLhlw button:hover{color:#00a0d2}.MLwfZfK5uVZOtIHI1cdt{background:#fff9db}.MLwfZfK5uVZOtIHI1cdt .nx2ZqeD9AnYnPnKHAqKJ{color:#fab007}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,3 @@
|
|||
.xAYNgmh_FT28wOZEe4og{background-color:#fff;margin-left:20px;padding:1rem 2rem 2rem}@media only screen and (min-width:1024px){.xAYNgmh_FT28wOZEe4og{max-width:1000px}}.W3wz4Liah2EvWxtTBXN8{align-items:center;display:flex}.OpLLWfmNs6BXGmnmuinK{padding-right:1em}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt{background-color:#fda09a;border-radius:5px;display:flex;margin:1em;max-width:450px;padding:1em}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt .A2dLn8oZtVzuXngZMDRp{margin-bottom:auto;margin-top:auto}div.OpLLWfmNs6BXGmnmuinK.q0fCXPnTi1vRhNmP0IEt .xFoMk9Jc8Ir4n5Olcce1{max-width:400px}.OpLLWfmNs6BXGmnmuinK .xFoMk9Jc8Ir4n5Olcce1{padding-right:1em}.OpLLWfmNs6BXGmnmuinK.fQeEY3YNz4yh6R7vdi7J .JPBgwk6PxfiitLxJLE54{color:green}h2.VklefjWwawC59yrOPe3e{font-size:18px}h3.VklefjWwawC59yrOPe3e{font-size:16px}.e8Vu3y2YBkuW8N9IhY2m{margin-bottom:1rem;margin-top:1rem}.gNYVG50hxMZs8Gqbj_T0 th{font-weight:700}button.dpYyb_l0GWlAiVkOmmYt{background:none;border:0}.WJl_9YHKGkhUvtVwgVco{align-items:center;display:flex}.HBCEbIhIET1XISEYneSA{margin:1rem}button.ZXe2iyFqFThwx_UF4CBf{background:#008ded;border:solid #0064b1;border-radius:3px;border-width:1px 1px 4px;color:#fff;cursor:pointer;font-size:14px;font-weight:600;line-height:1.4em;padding:.7em 1.5em}button.ZXe2iyFqFThwx_UF4CBf[disabled]{background:#f8f9fa;border:1px solid #f8f9fa;color:#008ded;cursor:default}button .HgLyUkphZYd8YsLSMJAZ{display:inline-block;min-width:3.2em;text-align:right}.Gu2u4ZSZT25Yqm8zSogj{background-color:#fdfdf3;border:1px solid #000;border-radius:5px;max-width:600px;padding:1.5em}.WOV9bdVrpJVdQWzhBnHZ{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}@media only screen and (min-width:1024px){.QN_KH8sqi5QFBDqaH1rI{display:flex}}.bBMVcUUJf1GW7veG1Zic{flex-direction:row}.pIa2BGO1ABMtYZY185Bf{flex-direction:column}.h0koIl1zvME7toM3jUk9{position:relative}.kWqY7l_wn27HmdUNz7ZY .JPBgwk6PxfiitLxJLE54{font-size:24px}.kWqY7l_wn27HmdUNz7ZY.q3No9l7YMUEH1xvYTNfI .JPBgwk6PxfiitLxJLE54,.Y7M4JHzDp7jtCt6MonbK{color:green}.a6qTuZmDiKS_FHgMZawo,.kWqY7l_wn27HmdUNz7ZY.PwCQsIQEdGz9b0cOj3iA .JPBgwk6PxfiitLxJLE54{color:red}.kWqY7l_wn27HmdUNz7ZY.Gu2u4ZSZT25Yqm8zSogj .JPBgwk6PxfiitLxJLE54,.rw5FUVRrrdM17WyxcRZ9{color:#b7b700}.QEoklKhbCbwOUBs0cspa{margin-right:1rem}.oWHnpotXuoOIlJoqkkgw~label .NzRaF0U8aKPVtS6JIaK8,.gIUwcNcpOHhTKG4sTlfg~label .NzRaF0U8aKPVtS6JIaK8{display:none;opacity:0}.oWHnpotXuoOIlJoqkkgw:checked~label .NzRaF0U8aKPVtS6JIaK8,.gIUwcNcpOHhTKG4sTlfg:checked~label .NzRaF0U8aKPVtS6JIaK8{color:#228be6;display:block;opacity:1}.oWHnpotXuoOIlJoqkkgw:checked~label .iemYJRvB4tzF1xnuGiAw,.gIUwcNcpOHhTKG4sTlfg:checked~label .iemYJRvB4tzF1xnuGiAw{display:none;opacity:0}.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS,.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS a{color:#495057}.oWHnpotXuoOIlJoqkkgw:checked~label .BFR5diS8tiViycbuTDVS a:hover,.oWHnpotXuoOIlJoqkkgw~label .BFR5diS8tiViycbuTDVS a:hover{color:#228be6;text-decoration-color:initial}
|
||||
.iVV55iNB320NJJLspy7m{max-width:600px}.BcmdF5mOoQ3Luug6sJrn{border:1px dotted grey;border-radius:5px;margin:1rem 0 1rem 1rem;padding:1rem}
|
||||
.lX8h3LbX6kaLN7_hLhlw{align-items:stretch;background-color:#e4f6ff;border-radius:.25rem;color:#495057;display:flex;flex-direction:row;flex-wrap:no-wrap;margin-top:1rem;max-width:800px}.nx2ZqeD9AnYnPnKHAqKJ{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem;color:#008bed;font-size:1rem;padding:.55rem .75rem .5rem .25rem}.ovRzytWn5jGccLKV78T9{font-size:.8rem;font-weight:600;line-height:1.5;margin-bottom:.2rem;margin-top:0}.M_C6Dj_EqhO8IuY52iA6{display:flex;flex-direction:column;flex-grow:1;font-size:.8rem;padding:.5rem .25rem .75rem 1rem}.M_C6Dj_EqhO8IuY52iA6 p{margin-bottom:.5rem;margin-top:0}.M_C6Dj_EqhO8IuY52iA6 svg{font-size:.7rem}.M_C6Dj_EqhO8IuY52iA6 ul{margin:0}.M_C6Dj_EqhO8IuY52iA6 li{display:inline-block;margin-bottom:0;padding-left:1rem}.liWjpcvKZkKaYPsJjQPA{margin-top:1rem}.lX8h3LbX6kaLN7_hLhlw button{color:#0073aa}.lX8h3LbX6kaLN7_hLhlw button:hover{color:#00a0d2}.MLwfZfK5uVZOtIHI1cdt{background:#fff9db}.MLwfZfK5uVZOtIHI1cdt .nx2ZqeD9AnYnPnKHAqKJ{color:#fab007}
|
|
@ -1,698 +0,0 @@
|
|||
.d7wuKQTkcJufIbd\+gVhKnw\=\= {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.F6JwARlyXPrf\+kIygaN8dQ\=\= {
|
||||
padding: 1rem;
|
||||
margin: 1rem 1rem 1rem 0rem;
|
||||
border: 1px dotted grey;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.v2APGCcZUAaU68TnPHhvxw\=\= {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: no-wrap;
|
||||
align-items: stretch;
|
||||
background-color: #E4F6FF;
|
||||
color: rgb(73, 80, 87);
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 1rem;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.syPwBWS1kp-zUKz4hcgcXg\=\= {
|
||||
color: #008BED;
|
||||
border-top-left-radius: 0.25rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
padding: .55rem .25rem .5rem .75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.ptjLX6BwJtUff-P6OkZBiA\=\= {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.2rem;
|
||||
font-size: .8rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: .5rem 1rem .75rem .25rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= p {
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= svg {
|
||||
font-size: .7rem;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.VAB708TLB4qhUVdnQGAxJA\=\= li {
|
||||
display: inline-block;
|
||||
padding-right: 1rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.CIIJrcA\+PLxU-W4xIVozXw\=\= {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.v2APGCcZUAaU68TnPHhvxw\=\= button {
|
||||
color: #0073aa;
|
||||
}
|
||||
|
||||
.v2APGCcZUAaU68TnPHhvxw\=\= button:hover {
|
||||
color: #00a0d2;
|
||||
}
|
||||
|
||||
/* type: warning */
|
||||
.iAbTOYj3VuCpNr1NEwmL4g\=\= {
|
||||
background: rgb(255, 249, 219);
|
||||
}
|
||||
|
||||
.iAbTOYj3VuCpNr1NEwmL4g\=\= .syPwBWS1kp-zUKz4hcgcXg\=\= {
|
||||
color: rgb(250, 176, 7);
|
||||
}
|
||||
|
||||
|
||||
.QIGY8oMzb821jqSAA6Rn4g\=\= {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.aALIlyz\+TBWS9MLVeh6wmA\=\= button {
|
||||
font-size: 1.5em;
|
||||
background: transparent;
|
||||
margin-right: 0.125em;
|
||||
margin-left: 0.125em;
|
||||
border: none;
|
||||
padding-bottom: 0.25em;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.aALIlyz\+TBWS9MLVeh6wmA\=\= button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aALIlyz\+TBWS9MLVeh6wmA\=\= button:disabled {
|
||||
cursor: default;
|
||||
border-bottom: 4px solid #008DED;
|
||||
color: unset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
._0x0RK8KiAJgDczKK031HxQ\=\= {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.hyAooutsnRTCptVmz96wbg\=\= {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hyAooutsnRTCptVmz96wbg\=\= svg {
|
||||
margin-right: .5em;
|
||||
margin-bottom: .125em;
|
||||
}
|
||||
|
||||
.BawvAG3StpFE9UbNiPvK3g\=\= {
|
||||
width: 30%;
|
||||
font-weight: bold;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.w9F7a0jctEk6w8ugAV5ABA\=\= {
|
||||
width: 1rem;
|
||||
margin-right: .25rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.BRC\+YZNrGUxfH82gZsrdwA\=\= {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.tuTbOAR46RTbuDXH00d2gQ\=\= {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.BnGZOlJKEKc\+YI9KmQh\+Yg\=\= {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 70%;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1024px) {
|
||||
.BnGZOlJKEKc\+YI9KmQh\+Yg\=\= {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.iCLEbIAXAx8VH8RGCCbvgg\=\= {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.iCLEbIAXAx8VH8RGCCbvgg\=\= > ul {
|
||||
list-style: none;
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.QZrz215uMWhNgkyzgTmOag\=\= {
|
||||
display: flex;
|
||||
flex-direction: flex-row;
|
||||
}
|
||||
|
||||
.zjk9n76WnC7PKioF3aBPNA\=\= {
|
||||
margin-left: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.JgyF2w3zr8u8EZtHirZ9jw\=\= {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.l90FgLRSjN56djI2bGkw0A\=\= {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.l90FgLRSjN56djI2bGkw0A\=\= button {
|
||||
background: none;
|
||||
border: none;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.l90FgLRSjN56djI2bGkw0A\=\= button svg {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.nszEb3XbyS\+sLm8WSjoWSw\=\= .tuTbOAR46RTbuDXH00d2gQ\=\= {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.m4T9E7AFyJOV9pxI900Q3Q\=\= {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
margin-top: .25rem;
|
||||
color: #868e96;
|
||||
}
|
||||
|
||||
.m4T9E7AFyJOV9pxI900Q3Q\=\= a {
|
||||
color: #868e96;
|
||||
}
|
||||
|
||||
.m4T9E7AFyJOV9pxI900Q3Q\=\= a:hover {
|
||||
color: #228be6;
|
||||
}
|
||||
|
||||
.-HAQ9msG6UCBe4TdGvJ5Gw\=\= {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.Ihb3kyONjVgEWV0zB0vfxQ\=\= {
|
||||
margin-right: 20px;
|
||||
padding: 1rem 2rem 2rem 2rem;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1024px) {
|
||||
.Ihb3kyONjVgEWV0zB0vfxQ\=\= {
|
||||
max-width: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
.ToMOGjAmxSPs6D\+glWGx9A\=\= {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
._3edKMOIt9iftk0Zz-7ia9w\=\= {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
div._3edKMOIt9iftk0Zz-7ia9w\=\=.NbqVQtCU5W3ihwasrYexfA\=\= {
|
||||
display: flex;
|
||||
margin: 1em;
|
||||
background-color: #fda09a;
|
||||
border-radius: 5px;
|
||||
max-width: 450px;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
div._3edKMOIt9iftk0Zz-7ia9w\=\=.NbqVQtCU5W3ihwasrYexfA\=\= .gEl1fl\+2lk74ueiQz5cPxQ\=\= {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
div._3edKMOIt9iftk0Zz-7ia9w\=\=.NbqVQtCU5W3ihwasrYexfA\=\= .vumGDcuTrv0Ekcc\+McKiXw\=\= {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
._3edKMOIt9iftk0Zz-7ia9w\=\= .vumGDcuTrv0Ekcc\+McKiXw\=\= {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
._3edKMOIt9iftk0Zz-7ia9w\=\=._7IGgJmOfwN1O\+c0smUgA9Q\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\= {
|
||||
color: green;
|
||||
}
|
||||
|
||||
h2._3hBTOhCdvQjibQScAbzMIQ\=\= {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h3._3hBTOhCdvQjibQScAbzMIQ\=\= {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
._0XZF4B-SzNg4vm8Dl0F7TA\=\= {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.YGhcFGkqFAeqpY9iNYq9Sw\=\= th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button.mQ\+dGgo7ePYMdFVM7UJy3Q\=\= {
|
||||
border: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.LZqubosol4XlcTmVPXrgwA\=\= {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
._92G6m9T1MVtvbrtbN0ztew\=\= {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
button.vKD-ffoQma2PtYJ6syJLXA\=\= {
|
||||
border: 1px solid #0064B1;
|
||||
border-bottom: 4px solid #0064B1;
|
||||
border-radius: 3px;
|
||||
padding: .7em 1.5em;
|
||||
background: #008DED;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.vKD-ffoQma2PtYJ6syJLXA\=\=[disabled] {
|
||||
border: 1px solid #F8F9FA;
|
||||
background: #F8F9FA;
|
||||
color: #008DED;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
button .jRDHFr0fk9vh0tmPg3yyNA\=\= {
|
||||
display: inline-block;
|
||||
min-width: 3.2em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.BHff-dIr\+7jxh1slKud1UA\=\= {
|
||||
background-color: #fdfdf3;
|
||||
max-width: 600px;
|
||||
padding: 1.5em;
|
||||
border-radius: 5px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
._8sv48aq5xq1UY1HM-IXXWw\=\= {
|
||||
border: 0;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1024px) {
|
||||
.XWyrhxEjrFCimjviedIRKg\=\= {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.KIG-iO8JlK18PTTkxmFFfQ\=\= {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.wbV6cqcB6HeXauGMWUOUJw\=\= {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.PWf16KXgVsL5DasX-69r\+w\=\= {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.BFnd\+XqC\+F5AvKCZ6eMOOA\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\= {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.BFnd\+XqC\+F5AvKCZ6eMOOA\=\=.x86R9\+0TG6mMWrDQqDWMxQ\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\=, ._8fwuockVscy-LVkmd5sRrg\=\= {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.BFnd\+XqC\+F5AvKCZ6eMOOA\=\=._4ywMQ6iToIUtlBzG0klUZQ\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\=, .n2ieOzL8DYqXeYvR4dnlBQ\=\= {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.BFnd\+XqC\+F5AvKCZ6eMOOA\=\=.BHff-dIr\+7jxh1slKud1UA\=\= .b1NRGX9AXkY1BfJ1MCfPTw\=\=, .Kc0JjWetOt7bzIP5T5F-3g\=\= {
|
||||
color: #b7b700;
|
||||
}
|
||||
|
||||
.cdItXesO30xESmpowZCWVA\=\= {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.XrgwDjPx-AobEiR9810sug\=\= ~ label .wfGA8rTfLXMNYeed\+7P0mg\=\=, .YGOg\+3jg-Q6uUjZrsKJJBw\=\= ~ label .wfGA8rTfLXMNYeed\+7P0mg\=\= {
|
||||
display:none;
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
.XrgwDjPx-AobEiR9810sug\=\=:checked ~ label .wfGA8rTfLXMNYeed\+7P0mg\=\=, .YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .wfGA8rTfLXMNYeed\+7P0mg\=\={
|
||||
display:block;
|
||||
opacity:1.0;
|
||||
color: #228be6;
|
||||
}
|
||||
|
||||
.XrgwDjPx-AobEiR9810sug\=\=:checked ~ label .dxKTDsQBZGG-O07iRE7TNg\=\=, .YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .dxKTDsQBZGG-O07iRE7TNg\=\={
|
||||
display:none;
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
.YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .AX07i\+p1n9K\+g4HYk3mvOg\=\= {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .AX07i\+p1n9K\+g4HYk3mvOg\=\= a {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.YGOg\+3jg-Q6uUjZrsKJJBw\=\=:checked ~ label .AX07i\+p1n9K\+g4HYk3mvOg\=\= a:hover,
|
||||
.YGOg\+3jg-Q6uUjZrsKJJBw\=\= ~ label .AX07i\+p1n9K\+g4HYk3mvOg\=\= a:hover {
|
||||
color: #228be6;
|
||||
text-decoration-color: initial;
|
||||
}
|
||||
|
||||
|
||||
/* Kit tab */
|
||||
|
||||
._8vGwfwQ6XO5BsPMbXdXTiA\=\= {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* API token */
|
||||
|
||||
.\+8WVAaXCYDQCF\+JpeqOS\+w\=\= {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #DDE2E6;
|
||||
padding: 1rem 1rem 1rem 0;
|
||||
}
|
||||
|
||||
.\+8WVAaXCYDQCF\+JpeqOS\+w\=\= label {
|
||||
width: 30%;
|
||||
font-size: .9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.\+8WVAaXCYDQCF\+JpeqOS\+w\=\= label svg {
|
||||
padding-right: .5rem;
|
||||
color: #DDE2E6;
|
||||
}
|
||||
|
||||
.NNaEQMl0NwcAO9BQkOMVjA\=\= .\+8WVAaXCYDQCF\+JpeqOS\+w\=\= p {
|
||||
font-size: unset;
|
||||
font-weight: unset;
|
||||
}
|
||||
|
||||
.NNaEQMl0NwcAO9BQkOMVjA\=\= .Uk7ZhRxwlPWH-ZTew\+MGbw\=\= p svg {
|
||||
padding-right: .5rem;
|
||||
color: unset;
|
||||
}
|
||||
|
||||
.NNaEQMl0NwcAO9BQkOMVjA\=\= {
|
||||
display: flex;
|
||||
margin-bottom: .75rem;
|
||||
padding: .5rem 1rem 1rem 0;
|
||||
border-bottom: 1px solid #DDE2E6;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.\+8WVAaXCYDQCF\+JpeqOS\+w\=\=.EwijJfw66yu\+IIrz6lhCfg\=\= {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.Uk7ZhRxwlPWH-ZTew\+MGbw\=\= {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.Uk7ZhRxwlPWH-ZTew\+MGbw\=\=.EwijJfw66yu\+IIrz6lhCfg\=\= {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.NNaEQMl0NwcAO9BQkOMVjA\=\= p {
|
||||
margin: 0;
|
||||
padding: .5rem 0;
|
||||
font-size: .9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.Uk7ZhRxwlPWH-ZTew\+MGbw\=\= .EGcS\+kpw3Rpch27QNPDt8A\=\= span svg {
|
||||
padding-right: .5rem;
|
||||
color: #00C346;
|
||||
}
|
||||
|
||||
button.IMhiWaWNyRtqLVii4y5GUw\=\= {
|
||||
transition: background .1s ease-in;
|
||||
transition: 0.1s ease-in;
|
||||
display: inline-block;
|
||||
margin-left: -.1em;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
background-color: transparent;
|
||||
padding: .5rem 1rem;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.IMhiWaWNyRtqLVii4y5GUw\=\=:hover {
|
||||
background-color: #DA001D;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.oIc2sd4DIsbfgvBY8l-wPw\=\= button {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.gxUD1N\+WoBmH-EJlqniWkw\=\= {
|
||||
margin-left: 1rem;
|
||||
line-height: 2.15384615;
|
||||
border: none;
|
||||
background-color: none;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.gxUD1N\+WoBmH-EJlqniWkw\=\=:hover {
|
||||
text-decoration-color: black;
|
||||
}
|
||||
|
||||
/* Active kit info */
|
||||
.zPZer73GvIkmxZ7KreNJYA\=\= {
|
||||
margin-bottom: .75rem;
|
||||
border-bottom: 1px solid #DDE2E6;
|
||||
padding: .5rem 1rem 1rem 0;
|
||||
}
|
||||
|
||||
.J-fVpwkR2DjY\+-8RSoJYMw\=\= {
|
||||
margin: 0;
|
||||
padding: .5rem 0;
|
||||
font-size: .9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.J-fVpwkR2DjY\+-8RSoJYMw\=\=.lycO-c64O\+OgDI9YkMivUw\=\= svg {
|
||||
padding-right: .5rem;
|
||||
color: #00C346;
|
||||
}
|
||||
|
||||
.J-fVpwkR2DjY\+-8RSoJYMw\=\=.szbUIrRpgoLKxf9N4EyKLg\=\= svg {
|
||||
color: #F8F9FA;
|
||||
}
|
||||
|
||||
|
||||
/* Select/View kit data */
|
||||
|
||||
.-pzVBDa-b25g\+W8geRXnxg\=\= {
|
||||
padding: .5rem 1rem 1rem 0;
|
||||
}
|
||||
|
||||
.-FLmUWzoR6pLlIRSuFBBYA\=\= {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: .9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.-FLmUWzoR6pLlIRSuFBBYA\=\= svg {
|
||||
padding-right: .5rem;
|
||||
color: #DDE2E6;
|
||||
}
|
||||
|
||||
._4hIkSVrBxe2EsX5xI4X\+-w\=\= {
|
||||
margin-left: 1.8rem;
|
||||
}
|
||||
|
||||
button.PGpv2OOpBHnM9TF49ScwhQ\=\= {
|
||||
transition: background .1s ease-in;
|
||||
transition: 0.1s ease-in;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 0 0 0 .2rem;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
background-color: transparent;
|
||||
padding: .5rem;
|
||||
color: #228be6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.PGpv2OOpBHnM9TF49ScwhQ\=\=:hover {
|
||||
background-color: #1c7ed6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
button.PGpv2OOpBHnM9TF49ScwhQ\=\= span {
|
||||
padding-left: .5em;
|
||||
}
|
||||
|
||||
.TCMt1wxwCSzZQpcKueWmkQ\=\= .D-w2AnOwJY2bw5sGz5McjA\=\= {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
margin-right: 1rem;
|
||||
padding: .4rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Kit settings table */
|
||||
|
||||
.oJVokNJ-sIuA\+gdUpTLGJA\=\= {
|
||||
margin-left: 1.8rem;
|
||||
}
|
||||
|
||||
.EczpWfVlPxzdEaHRV3pz2A\=\= {
|
||||
width: 100%;
|
||||
margin: 0 0 1rem 0;
|
||||
border-collapse: collapse;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.EczpWfVlPxzdEaHRV3pz2A\=\= .-qghjrb3DvBDmVsGriWvTA\=\=,
|
||||
.EczpWfVlPxzdEaHRV3pz2A\=\= .YObcB6LSN4LrZ85qknFF-w\=\= {
|
||||
padding: .5rem;
|
||||
text-align: left;
|
||||
border-top: 1px solid #DDE2E6;
|
||||
border-bottom: 1px solid #DDE2E6;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.EczpWfVlPxzdEaHRV3pz2A\=\= .-qghjrb3DvBDmVsGriWvTA\=\= {
|
||||
font-weight: 600;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
._9FAtxV9kie\+OmOmzBfJSkA\=\= {
|
||||
display: block;
|
||||
margin-top: .25rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
color: #868e96;
|
||||
}
|
||||
|
||||
._9FAtxV9kie\+OmOmzBfJSkA\=\= a {
|
||||
color: #868e96;
|
||||
}
|
||||
|
||||
._9FAtxV9kie\+OmOmzBfJSkA\=\= a:hover {
|
||||
color: #228be6;
|
||||
}
|
||||
|
||||
.c\+uCz1PFg7ovVDH7oUd5Sw\=\= {
|
||||
display: flex;
|
||||
margin-bottom: .5rem;
|
||||
border-bottom: 2px solid #008DED;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.c\+uCz1PFg7ovVDH7oUd5Sw\=\= label {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.gRCffi6JWAwOkiNdtjZueA\=\= th.agGiNQAP3Ikmo1HKogxbiA\=\=, td.agGiNQAP3Ikmo1HKogxbiA\=\= {
|
||||
background-color: #FFE2E2;
|
||||
}
|
||||
|
||||
.B\+Bikng94RbtcQqPqBrHOA\=\= th {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.B\+Bikng94RbtcQqPqBrHOA\=\= th .iRlCLvShkhGZ\+9zO5xq6uw\=\= {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.B\+Bikng94RbtcQqPqBrHOA\=\= code {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
._9-S9B47DLrx4-SEnbiDx1Q\=\=, .b72L8oHK2yOpCV0to2lb7Q\=\= {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
.va5cD9uCSEXRsb4FfvMXgQ\=\= {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.VkiLBdIWC85apP6l5wk2TQ\=\= {
|
||||
border: 1px solid black;
|
||||
background-color: #fdfdf3;
|
||||
padding: 1.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.GaudPtB1hhO0HHjMCEqy4Q\=\= {
|
||||
padding: .5rem;
|
||||
background-color: rgba(0,0,0,0);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.GaudPtB1hhO0HHjMCEqy4Q\=\=:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.GaudPtB1hhO0HHjMCEqy4Q\=\= .sp3bMuQjz4\+THc5BC0vcRg\=\= {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
"use strict";(self.webpackChunkfont_awesome_admin=self.webpackChunkfont_awesome_admin||[]).push([[897],{897:(c,e,a)=>{a.d(e,{GEE:()=>f,Nfw:()=>l,SGM:()=>i,wRm:()=>n});const i={prefix:"far",iconName:"circle-check",icon:[512,512,[61533,"check-circle"],"f058","M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-111 111-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L369 209z"]},l={prefix:"far",iconName:"square",icon:[448,512,[9632,9723,9724,61590],"f0c8","M384 80c8.8 0 16 7.2 16 16l0 320c0 8.8-7.2 16-16 16L64 432c-8.8 0-16-7.2-16-16L48 96c0-8.8 7.2-16 16-16l320 0zM64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32z"]},f={prefix:"far",iconName:"circle",icon:[512,512,[128308,128309,128992,128993,128994,128995,128996,9679,9898,9899,11044,61708,61915],"f111","M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z"]},n={prefix:"far",iconName:"circle-question",icon:[512,512,[62108,"question-circle"],"f059","M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm169.8-90.7c7.9-22.3 29.1-37.3 52.8-37.3l58.3 0c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24l0-13.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1l-58.3 0c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"]}}}]);
|
|
@ -1 +0,0 @@
|
|||
(window.webpackJsonp_font_awesome_admin=window.webpackJsonp_font_awesome_admin||[]).push([[9],{174:function(n,r,e){"use strict";e.d(r,"a",(function(){return t})),e.d(r,"d",(function(){return c})),e.d(r,"b",(function(){return i})),e.d(r,"c",(function(){return o}));var t={prefix:"far",iconName:"circle-check",icon:[512,512,[61533,"check-circle"],"f058","M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-111 111-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L369 209z"]},c={prefix:"far",iconName:"square",icon:[448,512,[9632,9723,9724,61590],"f0c8","M384 80c8.8 0 16 7.2 16 16V416c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V96c0-8.8 7.2-16 16-16H384zM64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64z"]},i={prefix:"far",iconName:"circle",icon:[512,512,[128308,128309,128992,128993,128994,128995,128996,9679,9898,9899,11044,61708,61915],"f111","M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z"]},o={prefix:"far",iconName:"circle-question",icon:[512,512,[62108,"question-circle"],"f059","M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm169.8-90.7c7.9-22.3 29.1-37.3 52.8-37.3h58.3c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24V250.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1H222.6c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"]}},258:function(n,r,e){var t=e(60),c=e(201),i=e(54),o=e(7),f=e(259),u=e(53),a=e(202),s=e(183),l=e(59),v=/\w*$/;n.exports=function(n,r){var e=30,p="...";if(o(r)){var x="separator"in r?r.separator:x;e="length"in r?s(r.length):e,p="omission"in r?t(r.omission):p}var h=(n=l(n)).length;if(i(n)){var g=a(n);h=g.length}if(e>=h)return n;var d=e-u(p);if(d<1)return p;var m=g?c(g,0,d).join(""):n.slice(0,d);if(void 0===x)return m+p;if(g&&(d+=m.length-d),f(x)){if(n.slice(d).search(x)){var z,w=m;for(x.global||(x=RegExp(x.source,l(v.exec(x))+"g")),x.lastIndex=0;z=x.exec(w);)var M=z.index;m=m.slice(0,void 0===M?d:M)}}else if(n.indexOf(t(x),d)!=d){var _=m.lastIndexOf(x);_>-1&&(m=m.slice(0,_))}return m+p}},259:function(n,r,e){var t=e(260),c=e(17),i=e(19),o=i&&i.isRegExp,f=o?c(o):t;n.exports=f},260:function(n,r,e){var t=e(4),c=e(3);n.exports=function(n){return c(n)&&"[object RegExp]"==t(n)}},263:function(n,r,e){var t=e(182);n.exports=function(n,r){return t(n,r)}},264:function(n,r,e){var t=e(265);n.exports=function(n){return n&&n.length?t(n):[]}},265:function(n,r,e){var t=e(33);n.exports=function(n,r){for(var e=-1,c=n.length,i=0,o=[];++e<c;){var f=n[e],u=r?r(f):f;if(!e||!t(u,a)){var a=u;o[i++]=0===f?0:f}}return o}},266:function(n,r,e){var t=e(267),c=e(67),i=e(273),o=e(274),f=i((function(n,r){return o(n)?t(n,c(r,1,o,!0)):[]}));n.exports=f},267:function(n,r,e){var t=e(195),c=e(268),i=e(272),o=e(32),f=e(17),u=e(196);n.exports=function(n,r,e,a){var s=-1,l=c,v=!0,p=n.length,x=[],h=r.length;if(!p)return x;e&&(r=o(r,f(e))),a?(l=i,v=!1):r.length>=200&&(l=u,v=!1,r=new t(r));n:for(;++s<p;){var g=n[s],d=null==e?g:e(g);if(g=a||0!==g?g:0,v&&d==d){for(var m=h;m--;)if(r[m]===d)continue n;x.push(g)}else l(r,d,a)||x.push(g)}return x}},268:function(n,r,e){var t=e(269);n.exports=function(n,r){return!(null==n||!n.length)&&t(n,r,0)>-1}},269:function(n,r,e){var t=e(200),c=e(270),i=e(271);n.exports=function(n,r,e){return r==r?i(n,r,e):t(n,c,e)}},270:function(n,r){n.exports=function(n){return n!=n}},271:function(n,r){n.exports=function(n,r,e){for(var t=e-1,c=n.length;++t<c;)if(n[t]===r)return t;return-1}},272:function(n,r){n.exports=function(n,r,e){for(var t=-1,c=null==n?0:n.length;++t<c;)if(e(r,n[t]))return!0;return!1}},273:function(n,r,e){var t=e(62),c=e(68),i=e(69);n.exports=function(n,r){return i(c(n,r,t),n+"")}},274:function(n,r,e){var t=e(16),c=e(3);n.exports=function(n){return c(n)&&t(n)}}}]);
|
|
@ -0,0 +1 @@
|
|||
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-dom-ready', 'wp-element', 'wp-i18n'), 'version' => 'c533036ab18a17f3f9bc');
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -2,7 +2,7 @@ const defaultConfig = require('@wordpress/scripts/config/jest-e2e.config')
|
|||
require('dotenv').config({ path: '../.env' })
|
||||
|
||||
process.env.WP_BASE_URL = `http://${process.env.WP_DOMAIN}`
|
||||
process.env.WP_USERNAME=process.env.WP_ADMIN_USERNAME
|
||||
process.env.WP_PASSWORD=process.env.WP_ADMIN_PASSWORD
|
||||
process.env.WP_USERNAME = process.env.WP_ADMIN_USERNAME
|
||||
process.env.WP_PASSWORD = process.env.WP_ADMIN_PASSWORD
|
||||
|
||||
module.exports = defaultConfig;
|
||||
module.exports = defaultConfig
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,27 +1,24 @@
|
|||
{
|
||||
"name": "font-awesome-admin",
|
||||
"version": "4.4.0",
|
||||
"version": "5.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fa-icon-chooser-react": "^0.5.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.3",
|
||||
"@wordpress/components": "14.1.10",
|
||||
"axios": "^0.25.0",
|
||||
"classnames": "^2.2.6",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-shadow-dom-retarget-events": "^1.0.11",
|
||||
"redux": "^4.0.4",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"web-vitals": "^1.0.1",
|
||||
"use-subscription": "1.5.1",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.35"
|
||||
"axios": "^1.7.4",
|
||||
"classnames": "^2.5.1",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"react-redux": "^8",
|
||||
"react-shadow-dom-retarget-events": "^1.1.0",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"web-vitals": "^4.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "wp-scripts build --webpack-no-externals",
|
||||
"build": "wp-scripts build",
|
||||
"check-engines": "wp-scripts check-engines",
|
||||
"check-licenses": "wp-scripts check-licenses",
|
||||
"format": "wp-scripts format",
|
||||
|
@ -31,8 +28,9 @@
|
|||
"lint:md:js": "wp-scripts lint-md-js",
|
||||
"lint:pkg-json": "wp-scripts lint-pkg-json",
|
||||
"packages-update": "wp-scripts packages-update",
|
||||
"start": "wp-scripts start --webpack-no-externals",
|
||||
"start": "wp-scripts start",
|
||||
"test:e2e": "wp-scripts test-e2e",
|
||||
"test:playwright": "npx playwright test",
|
||||
"test:unit": "wp-scripts test-unit-js",
|
||||
"test": "npm run test:unit"
|
||||
},
|
||||
|
@ -55,30 +53,27 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wordpress/babel-preset-default": "6.2.0",
|
||||
"@wordpress/components": "14.1.10",
|
||||
"@wordpress/compose": "4.1.5",
|
||||
"@wordpress/data": "5.1.5",
|
||||
"@wordpress/e2e-test-utils": "^5.4.10",
|
||||
"@wordpress/element": "3.1.1",
|
||||
"@wordpress/i18n": "^3.16.0",
|
||||
"@wordpress/icons": "4.0.2",
|
||||
"@wordpress/jest-preset-default": "7.0.5",
|
||||
"@wordpress/primitives": "2.1.1",
|
||||
"@wordpress/rich-text": "4.1.5",
|
||||
"@wordpress/scripts": "16.1.4",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"dotenv": "^14.2.0",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.0",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-test-renderer": "16.13.1",
|
||||
"@wordpress/babel-preset-default": "wp-6.7",
|
||||
"@wordpress/block-editor": "wp-6.7",
|
||||
"@wordpress/blocks": "wp-6.7",
|
||||
"@wordpress/components": "wp-6.7",
|
||||
"@wordpress/compose": "wp-6.7",
|
||||
"@wordpress/data": "wp-6.7",
|
||||
"@wordpress/e2e-test-utils": "wp-6.7",
|
||||
"@wordpress/e2e-test-utils-playwright": "^0.26.0",
|
||||
"@wordpress/i18n": "wp-6.7",
|
||||
"@wordpress/icons": "wp-6.7",
|
||||
"@wordpress/jest-preset-default": "wp-6.7",
|
||||
"@wordpress/keyboard-shortcuts": "wp-6.7",
|
||||
"@wordpress/scripts": "wp-6.7",
|
||||
"decode-uri-component": "^0.4.1",
|
||||
"dotenv": "^14.3.2",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mysql2": "^3.9.9",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"rewire": "^5.0.0",
|
||||
"terser": "4.8.1",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"loader-utils": "^1.4.2",
|
||||
"decode-uri-component": "^0.2.1"
|
||||
"rewire": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import './src/playwright/support/env.js'
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
const testDir = 'src/playwright'
|
||||
const baseURL = `http://${process.env.WP_DOMAIN}`
|
||||
process.env.WP_BASE_URL = baseURL
|
||||
const adminStorageStatePath = 'src/playwright/.auth/state.json'
|
||||
|
||||
export default defineConfig({
|
||||
use: {
|
||||
baseURL
|
||||
},
|
||||
projects: [
|
||||
{ name: 'auth', testDir, testMatch: 'setup/auth.js' },
|
||||
{
|
||||
name: 'reset',
|
||||
testDir,
|
||||
testMatch: 'setup/reset.js',
|
||||
use: {
|
||||
storageState: adminStorageStatePath
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'setupProKit',
|
||||
testDir,
|
||||
testMatch: 'setup/proKit.js',
|
||||
use: {
|
||||
storageState: adminStorageStatePath
|
||||
},
|
||||
dependencies: ['auth', 'reset']
|
||||
},
|
||||
{
|
||||
name: 'with-proKit-chromium',
|
||||
testMatch: 'withProKit/*.spec.js',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
storageState: adminStorageStatePath
|
||||
},
|
||||
dependencies: ['setupProKit']
|
||||
},
|
||||
{
|
||||
name: 'withAuth-chromium',
|
||||
testMatch: 'withAuth/*.spec.js',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
storageState: adminStorageStatePath
|
||||
},
|
||||
dependencies: ['auth', 'reset']
|
||||
}
|
||||
]
|
||||
})
|
|
@ -3,51 +3,73 @@ import PropTypes from 'prop-types'
|
|||
import styles from './Alert.module.css'
|
||||
import classnames from 'classnames'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faInfoCircle,
|
||||
faThumbsUp,
|
||||
faSpinner,
|
||||
faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faInfoCircle, faThumbsUp, faSpinner, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
function getIcon(props = {}){
|
||||
switch(props.type){
|
||||
function getIcon(props = {}) {
|
||||
switch (props.type) {
|
||||
case 'info':
|
||||
return <FontAwesomeIcon icon={ faInfoCircle } title='info' fixedWidth />
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
icon={faInfoCircle}
|
||||
title="info"
|
||||
fixedWidth
|
||||
/>
|
||||
)
|
||||
case 'warning':
|
||||
return <FontAwesomeIcon icon={ faExclamationTriangle } title='warning' fixedWidth />
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationTriangle}
|
||||
title="warning"
|
||||
fixedWidth
|
||||
/>
|
||||
)
|
||||
case 'pending':
|
||||
return <FontAwesomeIcon icon={ faSpinner } title='pending' spin fixedWidth />
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinner}
|
||||
title="pending"
|
||||
spin
|
||||
fixedWidth
|
||||
/>
|
||||
)
|
||||
case 'success':
|
||||
return <FontAwesomeIcon icon={ faThumbsUp } title='success' fixedWidth />
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
icon={faThumbsUp}
|
||||
title="success"
|
||||
fixedWidth
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return <FontAwesomeIcon icon={ faExclamationTriangle } title='warning' fixedWidth />
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationTriangle}
|
||||
title="warning"
|
||||
fixedWidth
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function Alert(props = {}) {
|
||||
return <div className={ classnames(styles['alert'], styles[`alert-${ props.type }`]) } role="alert">
|
||||
<div className={ styles['alert-icon'] }>
|
||||
{ getIcon(props) }
|
||||
</div>
|
||||
<div className={ styles['alert-message'] }>
|
||||
<h2 className={ styles['alert-title'] }>
|
||||
{ props.title }
|
||||
</h2>
|
||||
<div className={ styles['alert-copy'] }>
|
||||
{ props.children }
|
||||
return (
|
||||
<div
|
||||
className={classnames(styles['alert'], styles[`alert-${props.type}`])}
|
||||
role="alert"
|
||||
>
|
||||
<div className={styles['alert-icon']}>{getIcon(props)}</div>
|
||||
<div className={styles['alert-message']}>
|
||||
<h2 className={styles['alert-title']}>{props.title}</h2>
|
||||
<div className={styles['alert-copy']}>{props.children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Alert.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf(['info', 'warning', 'success', 'pending']),
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.string,
|
||||
PropTypes.arrayOf(PropTypes.element)
|
||||
]).isRequired
|
||||
children: PropTypes.oneOfType([PropTypes.object, PropTypes.string, PropTypes.arrayOf(PropTypes.element)]).isRequired
|
||||
}
|
||||
|
||||
export default Alert
|
||||
|
|
|
@ -1,23 +1,15 @@
|
|||
import React from 'react'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import {
|
||||
addPendingOption,
|
||||
checkPreferenceConflicts
|
||||
} from './store/actions'
|
||||
import { addPendingOption, checkPreferenceConflicts } from './store/actions'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faDotCircle,
|
||||
faCheckSquare,
|
||||
faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faDotCircle, faCheckSquare, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faCircle, faSquare } from '@fortawesome/free-regular-svg-icons'
|
||||
import styles from './CdnConfigView.module.css'
|
||||
import sharedStyles from './App.module.css'
|
||||
import classnames from 'classnames'
|
||||
import has from 'lodash/has'
|
||||
import size from 'lodash/size'
|
||||
import { has, size, get } from 'lodash'
|
||||
import Alert from './Alert'
|
||||
import PropTypes from 'prop-types'
|
||||
import get from 'lodash/get'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
|
||||
const UNSPECIFIED = ''
|
||||
|
@ -29,21 +21,27 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
|||
const compat = useOption('compat')
|
||||
const pseudoElements = useOption('pseudoElements')
|
||||
const isVersion6 = !!version.match(/^6\./)
|
||||
const isVersion7 = !!version.match(/^7\./)
|
||||
const isVersion5 = !isVersion6 && !isVersion7
|
||||
|
||||
const pendingOptions = useSelector(state => state.pendingOptions)
|
||||
const pendingOptionConflicts = useSelector(state => state.pendingOptionConflicts)
|
||||
const hasChecked = useSelector(state => state.preferenceConflictDetection.hasChecked)
|
||||
const preferenceCheckSuccess = useSelector(state => state.preferenceConflictDetection.success)
|
||||
const preferenceCheckMessage = useSelector(state => state.preferenceConflictDetection.message)
|
||||
const pendingOptions = useSelector((state) => state.pendingOptions)
|
||||
const pendingOptionConflicts = useSelector((state) => state.pendingOptionConflicts)
|
||||
const hasChecked = useSelector((state) => state.preferenceConflictDetection.hasChecked)
|
||||
const preferenceCheckSuccess = useSelector((state) => state.preferenceConflictDetection.success)
|
||||
const preferenceCheckMessage = useSelector((state) => state.preferenceConflictDetection.message)
|
||||
|
||||
const versionOptions = useSelector(state => {
|
||||
const { releases: { available, latest_version_5, latest_version_6 } } = state
|
||||
const versionOptions = useSelector((state) => {
|
||||
const {
|
||||
releases: { available, latest_version_5, latest_version_6, latest_version_7 }
|
||||
} = state
|
||||
|
||||
return available.reduce((acc, version) => {
|
||||
if( latest_version_5 === version ) {
|
||||
acc[version] = `${ version } (latest 5.x)`
|
||||
} else if( latest_version_6 === version ) {
|
||||
acc[version] = `${ version } (latest)`
|
||||
if (latest_version_5 === version) {
|
||||
acc[version] = `${version} (latest 5.x)`
|
||||
} else if (latest_version_6 === version) {
|
||||
acc[version] = `${version} (latest 6.x)`
|
||||
} else if (latest_version_7 === version) {
|
||||
acc[version] = `${version} (latest)`
|
||||
} else {
|
||||
acc[version] = version
|
||||
}
|
||||
|
@ -54,7 +52,7 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
|||
const dispatch = useDispatch()
|
||||
|
||||
function handleOptionChange(change = {}, check = true) {
|
||||
const pendingTechnology = get( change, 'technology' )
|
||||
const pendingTechnology = get(change, 'technology')
|
||||
|
||||
const adjustedChange = pendingTechnology
|
||||
? 'webfont' === pendingTechnology
|
||||
|
@ -67,23 +65,33 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
|||
}
|
||||
|
||||
function getDetectionStatusForOption(option) {
|
||||
if(has(pendingOptions, option)) {
|
||||
if ( hasChecked && ! preferenceCheckSuccess ) {
|
||||
return <Alert title={ __( 'Error checking preferences', 'font-awesome' ) } type='warning'>
|
||||
<p>{ preferenceCheckMessage }</p>
|
||||
</Alert>
|
||||
if (has(pendingOptions, option)) {
|
||||
if (hasChecked && !preferenceCheckSuccess) {
|
||||
return (
|
||||
<Alert
|
||||
title={__('Error checking preferences', 'font-awesome')}
|
||||
type="warning"
|
||||
>
|
||||
<p>{preferenceCheckMessage}</p>
|
||||
</Alert>
|
||||
)
|
||||
} else if (has(pendingOptionConflicts, option)) {
|
||||
return <Alert title={ __( 'Preference Conflict', 'font-awesome' ) } type='warning'>
|
||||
{
|
||||
size(pendingOptionConflicts[option]) > 1
|
||||
? <div>
|
||||
{ __( 'This change might cause problems for these themes or plugins', 'font-awesome' ) }: { pendingOptionConflicts[option].join(', ') }.
|
||||
return (
|
||||
<Alert
|
||||
title={__('Preference Conflict', 'font-awesome')}
|
||||
type="warning"
|
||||
>
|
||||
{size(pendingOptionConflicts[option]) > 1 ? (
|
||||
<div>
|
||||
{__('This change might cause problems for these themes or plugins', 'font-awesome')}: {pendingOptionConflicts[option].join(', ')}.
|
||||
</div>
|
||||
: <div>
|
||||
{ __( 'This change might cause problems for the theme or plugin', 'font-awesome' ) }: { pendingOptionConflicts[option][0] }.
|
||||
</div>
|
||||
}
|
||||
</Alert>
|
||||
) : (
|
||||
<div>
|
||||
{__('This change might cause problems for the theme or plugin', 'font-awesome')}: {pendingOptionConflicts[option][0]}.
|
||||
</div>
|
||||
)}
|
||||
</Alert>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
@ -92,184 +100,225 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
|||
}
|
||||
}
|
||||
|
||||
return <div className={ classnames(styles['options-setter']) }>
|
||||
<form onSubmit={ e => e.preventDefault() }>
|
||||
<div className={ classnames( sharedStyles['flex'], sharedStyles['flex-row'] ) }>
|
||||
<div className={ styles['option-header'] }>Icons</div>
|
||||
<div className={ styles['option-choice-container'] }>
|
||||
<div className={ styles['option-choices'] }>
|
||||
<div className={ styles['option-choice'] }>
|
||||
return (
|
||||
<div className={classnames(styles['options-setter'])}>
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
<div className={classnames(sharedStyles['flex'], sharedStyles['flex-row'])}>
|
||||
<div className={styles['option-header']}>Icons</div>
|
||||
<div className={styles['option-choice-container']}>
|
||||
<div className={styles['option-choices']}>
|
||||
<div className={styles['option-choice']}>
|
||||
<input
|
||||
id="code_edit_icons_pro"
|
||||
name="code_edit_icons"
|
||||
type="radio"
|
||||
checked={ usePro }
|
||||
onChange={ () => handleOptionChange({ usePro: true }) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom']) }
|
||||
checked={usePro}
|
||||
onChange={() => handleOptionChange({ usePro: true })}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||
/>
|
||||
<label htmlFor="code_edit_icons_pro" className={ styles['option-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<FontAwesomeIcon
|
||||
icon={ faDotCircle }
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCircle }
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
<span className={ styles['option-label-text'] }>
|
||||
Pro
|
||||
</span>
|
||||
<label
|
||||
htmlFor="code_edit_icons_pro"
|
||||
className={styles['option-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={faDotCircle}
|
||||
className={sharedStyles['checked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCircle}
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
<span className={styles['option-label-text']}>Pro</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className={ styles['option-choice'] }>
|
||||
<div className={styles['option-choice']}>
|
||||
<input
|
||||
id="code_edit_icons_free"
|
||||
name="code_edit_icons"
|
||||
type="radio"
|
||||
checked={ ! usePro }
|
||||
onChange={ () => handleOptionChange({ usePro: false }) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom']) }
|
||||
checked={!usePro}
|
||||
onChange={() => handleOptionChange({ usePro: false })}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||
/>
|
||||
<label htmlFor="code_edit_icons_free" className={ styles['option-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<label
|
||||
htmlFor="code_edit_icons_free"
|
||||
className={styles['option-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={ faDotCircle }
|
||||
icon={faDotCircle}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
className={sharedStyles['checked-icon']}
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCircle }
|
||||
icon={faCircle}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
/>
|
||||
</span>
|
||||
<span className={ styles['option-label-text'] }>
|
||||
Free
|
||||
</span>
|
||||
<span className={styles['option-label-text']}>Free</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{ usePro &&
|
||||
isVersion6 &&
|
||||
<Alert title={ __( 'Heads up! Pro Version 6 is not available from CDN', 'font-awesome' ) } type='warning'>
|
||||
<p>You can, however, use a Kit. Make sure you have a paid subscription and select "Use a Kit" above. We'll walk you through the other details from there.</p>
|
||||
{usePro && (isVersion6 || isVersion7) && (
|
||||
<Alert
|
||||
title={isVersion6 ? __('Heads up! Pro Version 6 is not available from CDN', 'font-awesome') : __('Heads up! Pro Version 7 is not available from CDN', 'font-awesome')}
|
||||
type="warning"
|
||||
>
|
||||
<p>
|
||||
You can, however, use a Kit. Make sure you have an active Font Awesome subscription and select "Use a Kit" above. We'll walk you through the
|
||||
other details from there.
|
||||
</p>
|
||||
</Alert>
|
||||
}
|
||||
{ usePro &&
|
||||
!isVersion6 &&
|
||||
<Alert title={ __( 'Heads up! Pro requires a Font Awesome subscription', 'font-awesome' ) } type='info'>
|
||||
<p>And you need to add your WordPress site to the allowed domains for your CDN.</p>
|
||||
)}
|
||||
{usePro && isVersion5 && (
|
||||
<Alert
|
||||
title={__('Heads up! Pro requires a Font Awesome subscription', 'font-awesome')}
|
||||
type="info"
|
||||
>
|
||||
<p>And you need to add your WordPress site to the allowed domains for your CDN.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a rel="noopener noreferrer" target="_blank" href="https://fontawesome.com/account/cdn">{ __( 'Manage my allowed domains', 'font-awesome' ) }<FontAwesomeIcon icon={faExternalLinkAlt} style={{marginLeft: '.5em'}} /></a>
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
href="https://fontawesome.com/account/cdn"
|
||||
>
|
||||
{__('Manage my allowed domains', 'font-awesome')}
|
||||
<FontAwesomeIcon
|
||||
icon={faExternalLinkAlt}
|
||||
style={{ marginLeft: '.5em' }}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a rel="noopener noreferrer" target="_blank" href="https://fontawesome.com/pro">{ __( 'Get Pro', 'font-awesome' ) }<FontAwesomeIcon icon={faExternalLinkAlt} style={{marginLeft: '.5em'}} /></a>
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
href="https://fontawesome.com/pro"
|
||||
>
|
||||
{__('Get Pro', 'font-awesome')}
|
||||
<FontAwesomeIcon
|
||||
icon={faExternalLinkAlt}
|
||||
style={{ marginLeft: '.5em' }}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</Alert>
|
||||
}
|
||||
{ getDetectionStatusForOption('usePro') }
|
||||
)}
|
||||
{getDetectionStatusForOption('usePro')}
|
||||
</div>
|
||||
</div>
|
||||
<hr className={ styles['option-divider'] }/>
|
||||
<div className={ classnames( sharedStyles['flex'], sharedStyles['flex-row'] ) }>
|
||||
<div className={ styles['option-header'] }>{ __( 'Technology', 'font-awesome' ) }</div>
|
||||
<div className={ styles['option-choice-container'] }>
|
||||
<div className={ styles['option-choices'] }>
|
||||
<div className={ styles['option-choice'] }>
|
||||
<hr className={styles['option-divider']} />
|
||||
<div className={classnames(sharedStyles['flex'], sharedStyles['flex-row'])}>
|
||||
<div className={styles['option-header']}>{__('Technology', 'font-awesome')}</div>
|
||||
<div className={styles['option-choice-container']}>
|
||||
<div className={styles['option-choices']}>
|
||||
<div className={styles['option-choice']}>
|
||||
<input
|
||||
id="code_edit_tech_svg"
|
||||
name="code_edit_tech"
|
||||
type="radio"
|
||||
checked={ technology === 'svg' }
|
||||
onChange={ () => handleOptionChange({ technology: 'svg' }) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom']) }
|
||||
checked={technology === 'svg'}
|
||||
onChange={() => handleOptionChange({ technology: 'svg' })}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||
/>
|
||||
<label htmlFor="code_edit_tech_svg" className={ styles['option-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<label
|
||||
htmlFor="code_edit_tech_svg"
|
||||
className={styles['option-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={ faDotCircle }
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
icon={faDotCircle}
|
||||
className={sharedStyles['checked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCircle }
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
icon={faCircle}
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
<span className={ styles['option-label-text'] }>
|
||||
{ __( 'SVG', 'font-awesome' ) }
|
||||
</span>
|
||||
<span className={styles['option-label-text']}>{__('SVG', 'font-awesome')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className={ styles['option-choice'] }>
|
||||
<div className={styles['option-choice']}>
|
||||
<input
|
||||
id="code_edit_tech_webfont"
|
||||
name="code_edit_tech"
|
||||
type="radio"
|
||||
checked={ technology === 'webfont' }
|
||||
onChange={ () => handleOptionChange({
|
||||
technology: 'webfont',
|
||||
pseudoElements: false
|
||||
}) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom']) }
|
||||
checked={technology === 'webfont'}
|
||||
onChange={() =>
|
||||
handleOptionChange({
|
||||
technology: 'webfont',
|
||||
pseudoElements: false
|
||||
})
|
||||
}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||
/>
|
||||
<label htmlFor="code_edit_tech_webfont" className={ styles['option-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<FontAwesomeIcon
|
||||
icon={ faDotCircle }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCircle }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
/>
|
||||
</span>
|
||||
<span className={ styles['option-label-text'] }>
|
||||
{ __( 'Web Font', 'font-awesome' ) }
|
||||
{
|
||||
technology === 'webfont' &&
|
||||
<span className={styles['option-label-explanation']}>
|
||||
{ __( 'CSS Pseudo-elements are enabled by default with Web Font', 'font-awesome' ) }
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
<label
|
||||
htmlFor="code_edit_tech_webfont"
|
||||
className={styles['option-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={faDotCircle}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={sharedStyles['checked-icon']}
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCircle}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
/>
|
||||
</span>
|
||||
<span className={styles['option-label-text']}>
|
||||
{__('Web Font', 'font-awesome')}
|
||||
{technology === 'webfont' && (
|
||||
<span className={styles['option-label-explanation']}>
|
||||
{__('CSS Pseudo-elements are enabled by default with Web Font', 'font-awesome')}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{ getDetectionStatusForOption('technology') }
|
||||
{getDetectionStatusForOption('technology')}
|
||||
</div>
|
||||
</div>
|
||||
<div className={ classnames( sharedStyles['flex'], sharedStyles['flex-row'] ) }>
|
||||
<div className={ styles['option-header'] }></div>
|
||||
<div className={ styles['option-choice-container'] } style={{marginTop: '1em'}}>
|
||||
{ technology === 'svg' &&
|
||||
<div className={classnames(sharedStyles['flex'], sharedStyles['flex-row'])}>
|
||||
<div className={styles['option-header']}></div>
|
||||
<div
|
||||
className={styles['option-choice-container']}
|
||||
style={{ marginTop: '1em' }}
|
||||
>
|
||||
{technology === 'svg' && (
|
||||
<>
|
||||
<input
|
||||
id="code_edit_features_pseudo_elements"
|
||||
name="code_edit_features"
|
||||
type="checkbox"
|
||||
checked={ pseudoElements }
|
||||
checked={pseudoElements}
|
||||
onChange={() => handleOptionChange({ pseudoElements: !pseudoElements })}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
||||
/>
|
||||
<label htmlFor="code_edit_features_pseudo_elements" className={styles['option-label']}>
|
||||
<label
|
||||
htmlFor="code_edit_features_pseudo_elements"
|
||||
className={styles['option-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckSquare}
|
||||
|
@ -285,113 +334,129 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
|||
/>
|
||||
</span>
|
||||
<span className={styles['option-label-text']}>
|
||||
{ __( 'Enable CSS Pseudo-elements with SVG', 'font-awesome' ) }
|
||||
{__('Enable CSS Pseudo-elements with SVG', 'font-awesome')}
|
||||
<span className={styles['option-label-explanation']}>
|
||||
{ __( 'May cause performance issues.', 'font-awesome' ) } <a rel="noopener noreferrer" target="_blank" style={{marginLeft: '.5em'}} href="https://fontawesome.com/how-to-use/on-the-web/advanced/css-pseudo-elements">
|
||||
{ __( 'Learn more', 'font-awesome' ) } <FontAwesomeIcon icon={faExternalLinkAlt} style={{marginLeft: '.5em'}} />
|
||||
{__('May cause performance issues.', 'font-awesome')}{' '}
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
style={{ marginLeft: '.5em' }}
|
||||
href="https://fontawesome.com/how-to-use/on-the-web/advanced/css-pseudo-elements"
|
||||
>
|
||||
{__('Learn more', 'font-awesome')}{' '}
|
||||
<FontAwesomeIcon
|
||||
icon={faExternalLinkAlt}
|
||||
style={{ marginLeft: '.5em' }}
|
||||
/>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
{ getDetectionStatusForOption('pseudoElements') }
|
||||
{getDetectionStatusForOption('pseudoElements')}
|
||||
</>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<hr className={ styles['option-divider'] }/>
|
||||
<div className={ classnames( sharedStyles['flex'], sharedStyles['flex-row'] ) }>
|
||||
<div className={ styles['option-header'] }>Version</div>
|
||||
<div className={ styles['option-choice-container'] }>
|
||||
<div className={ styles['option-choices'] }>
|
||||
<hr className={styles['option-divider']} />
|
||||
<div className={classnames(sharedStyles['flex'], sharedStyles['flex-row'])}>
|
||||
<div className={styles['option-header']}>Version</div>
|
||||
<div className={styles['option-choice-container']}>
|
||||
<div className={styles['option-choices']}>
|
||||
<select
|
||||
className={ styles['version-select'] }
|
||||
className={styles['version-select']}
|
||||
name="version"
|
||||
onChange={ e => handleOptionChange({ version: e.target.value }) }
|
||||
value={ version }
|
||||
onChange={(e) => handleOptionChange({ version: e.target.value })}
|
||||
value={version}
|
||||
>
|
||||
{
|
||||
Object.keys(versionOptions).map((version, index) => {
|
||||
return <option key={ index } value={ version }>
|
||||
{ version === UNSPECIFIED ? '-' : versionOptions[version] }
|
||||
{Object.keys(versionOptions).map((version, index) => {
|
||||
return (
|
||||
<option
|
||||
key={index}
|
||||
value={version}
|
||||
>
|
||||
{version === UNSPECIFIED ? '-' : versionOptions[version]}
|
||||
</option>
|
||||
})
|
||||
}
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
{ getDetectionStatusForOption('version') }
|
||||
{getDetectionStatusForOption('version')}
|
||||
</div>
|
||||
</div>
|
||||
<hr className={ styles['option-divider'] }/>
|
||||
<div className={ classnames( sharedStyles['flex'], sharedStyles['flex-row'], styles['features'] ) }>
|
||||
<div className={ styles['option-header'] }>Older Version Compatibility</div>
|
||||
<div className={ styles['option-choice-container'] }>
|
||||
<div className={ styles['option-choices'] }>
|
||||
<div className={ styles['option-choice'] }>
|
||||
<hr className={styles['option-divider']} />
|
||||
<div className={classnames(sharedStyles['flex'], sharedStyles['flex-row'], styles['features'])}>
|
||||
<div className={styles['option-header']}>Older Version Compatibility</div>
|
||||
<div className={styles['option-choice-container']}>
|
||||
<div className={styles['option-choices']}>
|
||||
<div className={styles['option-choice']}>
|
||||
<input
|
||||
id="code_edit_compat_on"
|
||||
name="code_edit_compat_on"
|
||||
type="radio"
|
||||
value={ compat }
|
||||
checked={ compat }
|
||||
onChange={ () => handleOptionChange({ compat: ! compat }) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom']) }
|
||||
value={compat}
|
||||
checked={compat}
|
||||
onChange={() => handleOptionChange({ compat: !compat })}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||
/>
|
||||
<label htmlFor="code_edit_compat_on" className={ styles['option-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<label
|
||||
htmlFor="code_edit_compat_on"
|
||||
className={styles['option-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={ faDotCircle }
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
icon={faDotCircle}
|
||||
className={sharedStyles['checked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCircle }
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
icon={faCircle}
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
<span className={ styles['option-label-text'] }>
|
||||
{ __( 'On', 'font-awesome' ) }
|
||||
</span>
|
||||
<span className={styles['option-label-text']}>{__('On', 'font-awesome')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className={ styles['option-choice'] }>
|
||||
<div className={styles['option-choice']}>
|
||||
<input
|
||||
id="code_edit_v4_compat_off"
|
||||
name="code_edit_v4_compat_off"
|
||||
type="radio"
|
||||
value={ ! compat }
|
||||
checked={ ! compat }
|
||||
onChange={ () => handleOptionChange({ compat: ! compat }) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom']) }
|
||||
value={!compat}
|
||||
checked={!compat}
|
||||
onChange={() => handleOptionChange({ compat: !compat })}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||
/>
|
||||
<label htmlFor="code_edit_v4_compat_off" className={ styles['option-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<FontAwesomeIcon
|
||||
icon={ faDotCircle }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCircle }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
/>
|
||||
</span>
|
||||
<span className={ styles['option-label-text'] }>
|
||||
{ __( 'Off', 'font-awesome' ) }
|
||||
</span>
|
||||
<label
|
||||
htmlFor="code_edit_v4_compat_off"
|
||||
className={styles['option-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={faDotCircle}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={sharedStyles['checked-icon']}
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCircle}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
/>
|
||||
</span>
|
||||
<span className={styles['option-label-text']}>{__('Off', 'font-awesome')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{ getDetectionStatusForOption('compat') }
|
||||
{getDetectionStatusForOption('compat')}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
CdnConfigView.propTypes = {
|
||||
|
|
|
@ -6,9 +6,15 @@ import classnames from 'classnames'
|
|||
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
|
||||
export default function CheckingOptionStatusIndicator(){
|
||||
return <span className={ styles['checking-option-status-indicator'] }>
|
||||
<FontAwesomeIcon spin className={ classnames(sharedStyles['icon']) } icon={ faSpinner }/>
|
||||
{ __( 'checking for preference conflicts', 'font-awesome' ) }...
|
||||
</span>
|
||||
export default function CheckingOptionStatusIndicator() {
|
||||
return (
|
||||
<span className={styles['checking-option-status-indicator']}>
|
||||
<FontAwesomeIcon
|
||||
spin
|
||||
className={classnames(sharedStyles['icon'])}
|
||||
icon={faSpinner}
|
||||
/>
|
||||
{__('checking for preference conflicts', 'font-awesome')}...
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@ import React from 'react'
|
|||
import { useSelector } from 'react-redux'
|
||||
import styles from './ClientPreferencesView.module.css'
|
||||
import sharedStyles from './App.module.css'
|
||||
import find from 'lodash/find'
|
||||
import has from 'lodash/has'
|
||||
import size from 'lodash/size'
|
||||
import { find, has, size } from 'lodash'
|
||||
import classnames from 'classnames'
|
||||
import { __, sprintf } from '@wordpress/i18n'
|
||||
|
||||
|
@ -12,7 +10,7 @@ const UNSPECIFIED_INDICATOR = '-'
|
|||
|
||||
function formatVersionPreference(versionPreference = []) {
|
||||
return versionPreference
|
||||
.map(pref => `${pref[1]}${pref[0]}`)
|
||||
.map((pref) => `${pref[1]}${pref[0]}`)
|
||||
.join(
|
||||
sprintf(
|
||||
/* translators: 1: space */
|
||||
|
@ -23,96 +21,73 @@ function formatVersionPreference(versionPreference = []) {
|
|||
}
|
||||
|
||||
export default function ClientPreferencesView() {
|
||||
const clientPreferences = useSelector(state => state.clientPreferences)
|
||||
const conflicts = useSelector(state => state.preferenceConflicts)
|
||||
const clientPreferences = useSelector((state) => state.clientPreferences)
|
||||
const conflicts = useSelector((state) => state.preferenceConflicts)
|
||||
const hasAdditionalClients = size(clientPreferences)
|
||||
const hasConflicts = size(conflicts)
|
||||
|
||||
return <div className={ styles['client-requirements'] }>
|
||||
<h3 className={ sharedStyles['section-title'] }>{ __( 'Registered themes or plugins', 'font-awesome' ) }</h3>
|
||||
{
|
||||
hasAdditionalClients
|
||||
?
|
||||
return (
|
||||
<div className={styles['client-requirements']}>
|
||||
<h3 className={sharedStyles['section-title']}>{__('Registered themes or plugins', 'font-awesome')}</h3>
|
||||
{hasAdditionalClients ? (
|
||||
<div>
|
||||
<p className={sharedStyles['explanation']}>
|
||||
{
|
||||
__(
|
||||
'Below is the list of active themes or plugins using Font Awesome that have opted-in to share information about the settings they are expecting.',
|
||||
'font-awesome'
|
||||
)
|
||||
}
|
||||
|
||||
{ hasConflicts
|
||||
? <span className={sharedStyles['explanation']}>
|
||||
{ __( 'The highlights show where the settings are mismatched. You might want to adjust your settings to match, or your icons may not work as expected.', 'font-awesome' ) }
|
||||
</span>
|
||||
: null
|
||||
}</p>
|
||||
<table className={ classnames( 'widefat', 'striped' ) }>
|
||||
{__(
|
||||
'Below is the list of active themes or plugins using Font Awesome that have opted-in to share information about the settings they are expecting.',
|
||||
'font-awesome'
|
||||
)}
|
||||
|
||||
{hasConflicts ? (
|
||||
<span className={sharedStyles['explanation']}>
|
||||
{__(
|
||||
'The highlights show where the settings are mismatched. You might want to adjust your settings to match, or your icons may not work as expected.',
|
||||
'font-awesome'
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
</p>
|
||||
<table className={classnames('widefat', 'striped')}>
|
||||
<thead>
|
||||
<tr className={ sharedStyles['table-header'] }>
|
||||
<th>{ __( 'Name', 'font-awesome' ) }</th>
|
||||
<th className={ classnames({ [styles.conflicted]: !! conflicts['usePro'] }) }>{ __( 'Icons', 'font-awesome' ) }</th>
|
||||
<th className={ classnames({ [styles.conflicted]: !! conflicts['technology'] }) }>{ __( 'Technology', 'font-awesome' ) }</th>
|
||||
<th className={ classnames({ [styles.conflicted]: !! conflicts['version'] }) }>{ __( 'Version', 'font-awesome' ) }</th>
|
||||
<th className={ classnames({ [styles.conflicted]: !! conflicts['compat'] }) }>{ __( 'V4 Compat', 'font-awesome' ) }</th>
|
||||
<th className={ classnames({ [styles.conflicted]: !! conflicts['pseudoElements'] }) }>{ __( 'CSS Pseudo-elements', 'font-awesome' ) }</th>
|
||||
<tr className={sharedStyles['table-header']}>
|
||||
<th>{__('Name', 'font-awesome')}</th>
|
||||
<th className={classnames({ [styles.conflicted]: !!conflicts['usePro'] })}>{__('Icons', 'font-awesome')}</th>
|
||||
<th className={classnames({ [styles.conflicted]: !!conflicts['technology'] })}>{__('Technology', 'font-awesome')}</th>
|
||||
<th className={classnames({ [styles.conflicted]: !!conflicts['version'] })}>{__('Version', 'font-awesome')}</th>
|
||||
<th className={classnames({ [styles.conflicted]: !!conflicts['compat'] })}>{__('V4 Compat', 'font-awesome')}</th>
|
||||
<th className={classnames({ [styles.conflicted]: !!conflicts['pseudoElements'] })}>{__('CSS Pseudo-elements', 'font-awesome')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
Object.values(clientPreferences).map((client, index) => {
|
||||
const clientHasConflict = optionName => !!find(conflicts[optionName], c => c === client.name)
|
||||
{Object.values(clientPreferences).map((client, index) => {
|
||||
const clientHasConflict = (optionName) => !!find(conflicts[optionName], (c) => c === client.name)
|
||||
|
||||
return <tr key={ index }>
|
||||
<td>{ client.name }</td>
|
||||
<td
|
||||
className={
|
||||
classnames({ [styles.conflicted]: clientHasConflict('usePro') })
|
||||
}>
|
||||
{ has(client, 'usePro')
|
||||
? client.usePro ? 'Pro' : 'Free'
|
||||
: UNSPECIFIED_INDICATOR
|
||||
}
|
||||
</td>
|
||||
<td
|
||||
className={ classnames({ [styles.conflicted]: clientHasConflict('technology') }) }>
|
||||
{ has(client, 'technology')
|
||||
? client.technology
|
||||
: UNSPECIFIED_INDICATOR
|
||||
}
|
||||
</td>
|
||||
<td
|
||||
className={ classnames({ [styles.conflicted]: clientHasConflict('version') }) }>
|
||||
{ has(client, 'version')
|
||||
? formatVersionPreference(client.version)
|
||||
: UNSPECIFIED_INDICATOR
|
||||
}
|
||||
</td>
|
||||
<td
|
||||
className={ classnames({ [styles.conflicted]: clientHasConflict('compat') }) }>
|
||||
{ has(client, 'compat')
|
||||
? client.compat ? 'true' : 'false'
|
||||
: UNSPECIFIED_INDICATOR
|
||||
}
|
||||
</td>
|
||||
<td
|
||||
className={ classnames({ [styles.conflicted]: clientHasConflict('pseudoElements') }) }>
|
||||
{ has(client, 'pseudoElements')
|
||||
? client.pseudoElements ? 'true' : 'false'
|
||||
: UNSPECIFIED_INDICATOR
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
})
|
||||
}
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>{client.name}</td>
|
||||
<td className={classnames({ [styles.conflicted]: clientHasConflict('usePro') })}>
|
||||
{has(client, 'usePro') ? (client.usePro ? 'Pro' : 'Free') : UNSPECIFIED_INDICATOR}
|
||||
</td>
|
||||
<td className={classnames({ [styles.conflicted]: clientHasConflict('technology') })}>
|
||||
{has(client, 'technology') ? client.technology : UNSPECIFIED_INDICATOR}
|
||||
</td>
|
||||
<td className={classnames({ [styles.conflicted]: clientHasConflict('version') })}>
|
||||
{has(client, 'version') ? formatVersionPreference(client.version) : UNSPECIFIED_INDICATOR}
|
||||
</td>
|
||||
<td className={classnames({ [styles.conflicted]: clientHasConflict('compat') })}>
|
||||
{has(client, 'compat') ? (client.compat ? 'true' : 'false') : UNSPECIFIED_INDICATOR}
|
||||
</td>
|
||||
<td className={classnames({ [styles.conflicted]: clientHasConflict('pseudoElements') })}>
|
||||
{has(client, 'pseudoElements') ? (client.pseudoElements ? 'true' : 'false') : UNSPECIFIED_INDICATOR}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
:
|
||||
<p className={ sharedStyles['explanation'] }>
|
||||
{ __( 'No active themes or plugins have requested preferences for Font Awesome.', 'font-awesome' ) }
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
) : (
|
||||
<p className={sharedStyles['explanation']}>{__('No active themes or plugins have requested preferences for Font Awesome.', 'font-awesome')}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|||
import { faCheckCircle, faCog, faExclamationTriangle, faGrin, faSkull, faThumbsUp, faTimesCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
import { ADMIN_TAB_TROUBLESHOOT } from './store/reducers'
|
||||
import ConflictDetectionTimer from './ConflictDetectionTimer'
|
||||
import size from 'lodash/size'
|
||||
import has from 'lodash/has'
|
||||
import { has, size } from 'lodash'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import ErrorBoundary from './ErrorBoundary'
|
||||
|
||||
|
@ -19,50 +18,50 @@ import ErrorBoundary from './ErrorBoundary'
|
|||
const STATUS = {
|
||||
running: {
|
||||
code: 'Running',
|
||||
display: __( 'Running', 'font-awesome' )
|
||||
display: __('Running', 'font-awesome')
|
||||
},
|
||||
done: {
|
||||
code: 'Done',
|
||||
display: __( 'Done', 'font-awesome' )
|
||||
display: __('Done', 'font-awesome')
|
||||
},
|
||||
submitting: {
|
||||
code: 'Submitting',
|
||||
display: __( 'Submitting', 'font-awesome' )
|
||||
display: __('Submitting', 'font-awesome')
|
||||
},
|
||||
none: {
|
||||
code: 'None',
|
||||
display: __( 'None', 'font-awesome' )
|
||||
display: __('None', 'font-awesome')
|
||||
},
|
||||
error: {
|
||||
code: 'Error',
|
||||
display: __( 'Error', 'font-awesome' )
|
||||
display: __('Error', 'font-awesome')
|
||||
},
|
||||
expired: {
|
||||
code: 'Expired',
|
||||
display: __( 'Expired', 'font-awesome' )
|
||||
display: __('Expired', 'font-awesome')
|
||||
},
|
||||
ready: {
|
||||
code: 'Ready',
|
||||
display: __( 'Ready', 'font-awesome' )
|
||||
display: __('Ready', 'font-awesome')
|
||||
},
|
||||
stopped: {
|
||||
code: 'Stopped',
|
||||
display: __( 'Stopped', 'font-awesome' )
|
||||
display: __('Stopped', 'font-awesome')
|
||||
},
|
||||
stopping: {
|
||||
code: 'Stopping',
|
||||
display: __( 'Stopping', 'font-awesome' )
|
||||
display: __('Stopping', 'font-awesome')
|
||||
},
|
||||
restarting: {
|
||||
code: 'Restarting',
|
||||
display: __( 'Restarting', 'font-awesome' )
|
||||
display: __('Restarting', 'font-awesome')
|
||||
}
|
||||
}
|
||||
|
||||
const STYLES = {
|
||||
container: {
|
||||
position: 'fixed',
|
||||
fontFamily:'"Helvetica Neue",Helvetica,Arial,sans-serif',
|
||||
fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif',
|
||||
right: '10px',
|
||||
bottom: '10px',
|
||||
width: '450px',
|
||||
|
@ -82,7 +81,7 @@ const STYLES = {
|
|||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '5px 20px',
|
||||
padding: '5px 20px',
|
||||
color: '#CAECFF'
|
||||
},
|
||||
content: {
|
||||
|
@ -109,15 +108,15 @@ const STYLES = {
|
|||
color: '#fff'
|
||||
},
|
||||
tally: {
|
||||
display: 'flex',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
margin: '.5em 0',
|
||||
margin: '.5em 0',
|
||||
textAlign: 'center'
|
||||
},
|
||||
count: {
|
||||
flexBasis: '1em',
|
||||
marginRight: '5px',
|
||||
fontWeight: '600',
|
||||
fontWeight: '600',
|
||||
fontSize: '20px'
|
||||
},
|
||||
timerRow: {
|
||||
|
@ -125,7 +124,7 @@ const STYLES = {
|
|||
alignItems: 'center',
|
||||
backgroundColor: '#0064B1',
|
||||
padding: '10px 20px',
|
||||
color: '#fff',
|
||||
color: '#fff',
|
||||
fontWeight: '600'
|
||||
},
|
||||
button: {
|
||||
|
@ -133,7 +132,7 @@ const STYLES = {
|
|||
border: '0',
|
||||
padding: '5px',
|
||||
backgroundColor: 'transparent',
|
||||
color: '#fff',
|
||||
color: '#fff',
|
||||
opacity: '.7',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
|
@ -144,73 +143,59 @@ const STYLES = {
|
|||
}
|
||||
}
|
||||
|
||||
function withErrorBoundary( Component ) {
|
||||
function withErrorBoundary(Component) {
|
||||
return class extends ErrorBoundary {
|
||||
render() {
|
||||
return <div style={ STYLES.container }>
|
||||
{
|
||||
!!this.state.error
|
||||
? <div style={ STYLES.badness }>
|
||||
<FontAwesomeIcon icon={ faExclamationTriangle } />
|
||||
{
|
||||
__( ' Whoops, this is embarrassing! Some unexpected error has occurred. There might be some additional diagnostic information in the JavaScript console.', 'font-awesome' )
|
||||
}
|
||||
return (
|
||||
<div style={STYLES.container}>
|
||||
{!!this.state.error ? (
|
||||
<div style={STYLES.badness}>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} />
|
||||
{__(
|
||||
' Whoops, this is embarrassing! Some unexpected error has occurred. There might be some additional diagnostic information in the JavaScript console.',
|
||||
'font-awesome'
|
||||
)}
|
||||
</div>
|
||||
: <Component />
|
||||
}
|
||||
</div>
|
||||
) : (
|
||||
<Component />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ConflictDetectionReporter() {
|
||||
const dispatch = useDispatch()
|
||||
const settingsPageUrl = useSelector(state => state.settingsPageUrl)
|
||||
const settingsPageUrl = useSelector((state) => state.settingsPageUrl)
|
||||
const troubleshootTabUrl = `${settingsPageUrl}&tab=ts`
|
||||
const activeAdminTab = useSelector(state => state.activeAdminTab )
|
||||
const activeAdminTab = useSelector((state) => state.activeAdminTab)
|
||||
const currentlyOnPluginAdminPage = window.location.href.startsWith(settingsPageUrl)
|
||||
const currentlyOnTroubleshootTab = currentlyOnPluginAdminPage && activeAdminTab === ADMIN_TAB_TROUBLESHOOT
|
||||
const userAttemptedToStopScanner = useSelector(state => state.userAttemptedToStopScanner)
|
||||
const userAttemptedToStopScanner = useSelector((state) => state.userAttemptedToStopScanner)
|
||||
|
||||
const unregisteredClients = useSelector(
|
||||
state => state.unregisteredClients
|
||||
)
|
||||
const unregisteredClients = useSelector((state) => state.unregisteredClients)
|
||||
|
||||
const unregisteredClientsBeforeDetection = useSelector(
|
||||
state => state.unregisteredClientDetectionStatus.unregisteredClientsBeforeDetection
|
||||
)
|
||||
const unregisteredClientsBeforeDetection = useSelector((state) => state.unregisteredClientDetectionStatus.unregisteredClientsBeforeDetection)
|
||||
|
||||
const recentConflictsDetected = useSelector(
|
||||
state => state.unregisteredClientDetectionStatus.recentConflictsDetected
|
||||
)
|
||||
const recentConflictsDetected = useSelector((state) => state.unregisteredClientDetectionStatus.recentConflictsDetected)
|
||||
|
||||
const expired = useSelector(
|
||||
state => !state.showConflictDetectionReporter
|
||||
)
|
||||
const expired = useSelector((state) => !state.showConflictDetectionReporter)
|
||||
|
||||
const restarting = useSelector(
|
||||
state => expired && state.conflictDetectionScannerStatus.isSubmitting
|
||||
)
|
||||
const restarting = useSelector((state) => expired && state.conflictDetectionScannerStatus.isSubmitting)
|
||||
|
||||
const scannerReady = useSelector(
|
||||
state => state.conflictDetectionScannerStatus.hasSubmitted && state.conflictDetectionScannerStatus.success
|
||||
)
|
||||
const scannerReady = useSelector((state) => state.conflictDetectionScannerStatus.hasSubmitted && state.conflictDetectionScannerStatus.success)
|
||||
|
||||
const scannerIsStopping = useSelector(
|
||||
state => userAttemptedToStopScanner
|
||||
&& !state.conflictDetectionScannerStatus.hasSubmitted
|
||||
)
|
||||
const scannerIsStopping = useSelector((state) => userAttemptedToStopScanner && !state.conflictDetectionScannerStatus.hasSubmitted)
|
||||
|
||||
const userStoppedScannerSuccessfully = useSelector(
|
||||
state => userAttemptedToStopScanner
|
||||
&& !scannerIsStopping
|
||||
&& state.conflictDetectionScannerStatus.success
|
||||
(state) => userAttemptedToStopScanner && !scannerIsStopping && state.conflictDetectionScannerStatus.success
|
||||
)
|
||||
|
||||
const runStatus = useSelector(state => {
|
||||
const runStatus = useSelector((state) => {
|
||||
const { isSubmitting, hasSubmitted, success } = state.unregisteredClientDetectionStatus
|
||||
if (userAttemptedToStopScanner) {
|
||||
if ( scannerIsStopping ) {
|
||||
if (scannerIsStopping) {
|
||||
return STATUS.stopping
|
||||
} else if (userStoppedScannerSuccessfully) {
|
||||
return STATUS.stopped
|
||||
|
@ -221,131 +206,201 @@ function ConflictDetectionReporter() {
|
|||
}
|
||||
} else if (restarting) {
|
||||
return STATUS.restarting
|
||||
} else if ( expired ) {
|
||||
} else if (expired) {
|
||||
return STATUS.expired
|
||||
} else if (scannerReady) {
|
||||
return STATUS.ready
|
||||
} else if ( success && 0 === size( unregisteredClients ) ) {
|
||||
} else if (success && 0 === size(unregisteredClients)) {
|
||||
return STATUS.none
|
||||
} else if ( success ) {
|
||||
} else if (success) {
|
||||
return STATUS.done
|
||||
} else if( isSubmitting ) {
|
||||
} else if (isSubmitting) {
|
||||
return STATUS.submitting
|
||||
} else if( !hasSubmitted ){
|
||||
} else if (!hasSubmitted) {
|
||||
return STATUS.running
|
||||
} else {
|
||||
return STATUS.error
|
||||
}
|
||||
})
|
||||
|
||||
const errorMessage = useSelector(
|
||||
state => state.unregisteredClientDetectionStatus.message
|
||||
)
|
||||
const errorMessage = useSelector((state) => state.unregisteredClientDetectionStatus.message)
|
||||
|
||||
function stopScanner() {
|
||||
dispatch(userAttemptToStopScanner())
|
||||
dispatch(setConflictDetectionScanner({ enable: false }))
|
||||
}
|
||||
|
||||
const expiredOrStoppedDiv =
|
||||
const expiredOrStoppedDiv = (
|
||||
<div>
|
||||
<h2 style={ STYLES.tally }><span>{ size( unregisteredClients ) }</span> <span> { __( 'Results to Review', 'font-awesome' ) }</span></h2>
|
||||
<p style={ STYLES.p }>
|
||||
{
|
||||
currentlyOnTroubleshootTab
|
||||
? __( 'Manage results or restart the scanner here on the Troubleshoot tab.', 'font-awesome' )
|
||||
: <>
|
||||
{
|
||||
__( 'Manage results or restart the scanner on the Troubleshoot tab.', 'font-awesome' )
|
||||
} <a href={ troubleshootTabUrl } style={ STYLES.link }>{ __('Go', 'font-awesome' ) }</a>
|
||||
</>
|
||||
}
|
||||
<h2 style={STYLES.tally}>
|
||||
<span>{size(unregisteredClients)}</span> <span> {__('Results to Review', 'font-awesome')}</span>
|
||||
</h2>
|
||||
<p style={STYLES.p}>
|
||||
{currentlyOnTroubleshootTab ? (
|
||||
__('Manage results or restart the scanner here on the Troubleshoot tab.', 'font-awesome')
|
||||
) : (
|
||||
<>
|
||||
{__('Manage results or restart the scanner on the Troubleshoot tab.', 'font-awesome')}{' '}
|
||||
<a
|
||||
href={troubleshootTabUrl}
|
||||
style={STYLES.link}
|
||||
>
|
||||
{__('Go', 'font-awesome')}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
const stoppingOrSubmittingDiv =
|
||||
const stoppingOrSubmittingDiv = (
|
||||
<div>
|
||||
<div style={ STYLES.status }>
|
||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faCog } size="sm" spin /> <span>{ runStatus.display }</span></h2>
|
||||
<div style={STYLES.status}>
|
||||
<h2 style={STYLES.h2}>
|
||||
<FontAwesomeIcon
|
||||
icon={faCog}
|
||||
size="sm"
|
||||
spin
|
||||
/>{' '}
|
||||
<span>{runStatus.display}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={ STYLES.header }>
|
||||
<h1 style={ STYLES.h1 }>{ __( 'Font Awesome Conflict Scanner', 'font-awesome' ) }</h1>
|
||||
<p style={ STYLES.adminEyesOnly }>{ __( 'only admins can see this box', 'font-awesome' ) }</p>
|
||||
<div style={STYLES.header}>
|
||||
<h1 style={STYLES.h1}>{__('Font Awesome Conflict Scanner', 'font-awesome')}</h1>
|
||||
<p style={STYLES.adminEyesOnly}>{__('only admins can see this box', 'font-awesome')}</p>
|
||||
</div>
|
||||
<div style={ STYLES.content }>
|
||||
|
||||
<div style={STYLES.content}>
|
||||
{
|
||||
{
|
||||
None:
|
||||
None: (
|
||||
<div>
|
||||
<div style={ STYLES.status }>
|
||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faGrin } size="sm" /> <span>{ __( 'All clear!', 'font-awesome' ) }</span></h2>
|
||||
<p style={ STYLES.p }>{ __( 'No new conflicts found on this page.', 'font-awesome' ) }</p>
|
||||
<div style={STYLES.status}>
|
||||
<h2 style={STYLES.h2}>
|
||||
<FontAwesomeIcon
|
||||
icon={faGrin}
|
||||
size="sm"
|
||||
/>{' '}
|
||||
<span>{__('All clear!', 'font-awesome')}</span>
|
||||
</h2>
|
||||
<p style={STYLES.p}>{__('No new conflicts found on this page.', 'font-awesome')}</p>
|
||||
</div>
|
||||
</div>,
|
||||
Running:
|
||||
</div>
|
||||
),
|
||||
Running: (
|
||||
<div>
|
||||
<div style={ STYLES.status }>
|
||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faCog } size="sm" spin /> <span>{ __( 'Scanning', 'font-awesome' ) }...</span></h2>
|
||||
<div style={STYLES.status}>
|
||||
<h2 style={STYLES.h2}>
|
||||
<FontAwesomeIcon
|
||||
icon={faCog}
|
||||
size="sm"
|
||||
spin
|
||||
/>{' '}
|
||||
<span>{__('Scanning', 'font-awesome')}...</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>,
|
||||
Restarting:
|
||||
</div>
|
||||
),
|
||||
Restarting: (
|
||||
<div>
|
||||
<div style={ STYLES.status }>
|
||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faCog } size="sm" spin /> <span>{ __( 'Restarting', 'font-awesome' ) }...</span></h2>
|
||||
<div style={STYLES.status}>
|
||||
<h2 style={STYLES.h2}>
|
||||
<FontAwesomeIcon
|
||||
icon={faCog}
|
||||
size="sm"
|
||||
spin
|
||||
/>{' '}
|
||||
<span>{__('Restarting', 'font-awesome')}...</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>,
|
||||
Ready:
|
||||
</div>
|
||||
),
|
||||
Ready: (
|
||||
<div>
|
||||
<div>
|
||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faThumbsUp } size="sm" /> { __( 'Proton pack charged!', 'font-awesome' ) }</h2>
|
||||
<p style={ STYLES.p }>{ __( 'Wander through the pages of your web site and this scanner will track progress.', 'font-awesome' ) }</p>
|
||||
<h2 style={STYLES.h2}>
|
||||
<FontAwesomeIcon
|
||||
icon={faThumbsUp}
|
||||
size="sm"
|
||||
/>{' '}
|
||||
{__('Proton pack charged!', 'font-awesome')}
|
||||
</h2>
|
||||
<p style={STYLES.p}>{__('Wander through the pages of your web site and this scanner will track progress.', 'font-awesome')}</p>
|
||||
</div>
|
||||
</div>,
|
||||
</div>
|
||||
),
|
||||
Submitting: stoppingOrSubmittingDiv,
|
||||
Stopping: stoppingOrSubmittingDiv,
|
||||
Done:
|
||||
Done: (
|
||||
<div>
|
||||
<div style={ STYLES.status }>
|
||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faCheckCircle } size="sm" /> <span>{ __( 'Page scan complete', 'font-awesome' ) }</span></h2>
|
||||
<div style={STYLES.status}>
|
||||
<h2 style={STYLES.h2}>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
size="sm"
|
||||
/>{' '}
|
||||
<span>{__('Page scan complete', 'font-awesome')}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<p style={ STYLES.tally }><span style={ STYLES.count }>{ size( Object.keys( recentConflictsDetected ).filter(k => ! has(unregisteredClientsBeforeDetection, k) ) ) }</span> <span>{ __( 'new conflicts found on this page', 'font-awesome' ) }</span></p>
|
||||
<p style={ STYLES.tally }><span style={ STYLES.count }>{ size( unregisteredClients ) }</span> <span>total found</span>
|
||||
{
|
||||
currentlyOnTroubleshootTab ?
|
||||
<span> ({ __( 'manage conflicts here on the Troubleshoot tab', 'font-awesome' ) })</span>
|
||||
: <span> (<a href={ troubleshootTabUrl } style={ STYLES.link }>{ __( 'manage', 'font-awesome' ) }</a>)</span>
|
||||
}
|
||||
<p style={STYLES.tally}>
|
||||
<span style={STYLES.count}>{size(Object.keys(recentConflictsDetected).filter((k) => !has(unregisteredClientsBeforeDetection, k)))}</span>{' '}
|
||||
<span>{__('new conflicts found on this page', 'font-awesome')}</span>
|
||||
</p>
|
||||
</div>,
|
||||
Expired: expiredOrStoppedDiv,
|
||||
Stopped: expiredOrStoppedDiv,
|
||||
Error:
|
||||
<div>
|
||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faSkull } /> <span>{ __( 'Don\'t cross the streams! It would be bad.', 'font-awesome' ) }</span></h2>
|
||||
<p style={ STYLES.p }>
|
||||
{ errorMessage }
|
||||
<p style={STYLES.tally}>
|
||||
<span style={STYLES.count}>{size(unregisteredClients)}</span> <span>total found</span>
|
||||
{currentlyOnTroubleshootTab ? (
|
||||
<span> ({__('manage conflicts here on the Troubleshoot tab', 'font-awesome')})</span>
|
||||
) : (
|
||||
<span>
|
||||
(
|
||||
<a
|
||||
href={troubleshootTabUrl}
|
||||
style={STYLES.link}
|
||||
>
|
||||
{__('manage', 'font-awesome')}
|
||||
</a>
|
||||
)
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
Expired: expiredOrStoppedDiv,
|
||||
Stopped: expiredOrStoppedDiv,
|
||||
Error: (
|
||||
<div>
|
||||
<h2 style={STYLES.h2}>
|
||||
<FontAwesomeIcon icon={faSkull} /> <span>{__("Don't cross the streams! It would be bad.", 'font-awesome')}</span>
|
||||
</h2>
|
||||
<p style={STYLES.p}>{errorMessage}</p>
|
||||
</div>
|
||||
)
|
||||
}[runStatus.code]
|
||||
}
|
||||
</div>
|
||||
<div style={ STYLES.timerRow }>
|
||||
<div style={STYLES.timerRow}>
|
||||
<span>
|
||||
<ConflictDetectionTimer addDescription>
|
||||
<button style={ STYLES.button } title={ __( 'Stop timer', 'font-awesome' ) } onClick={() => stopScanner()}>
|
||||
<FontAwesomeIcon icon={ faTimesCircle } size="lg" />
|
||||
<button
|
||||
style={STYLES.button}
|
||||
title={__('Stop timer', 'font-awesome')}
|
||||
onClick={() => stopScanner()}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTimesCircle}
|
||||
size="lg"
|
||||
/>
|
||||
</button>
|
||||
</ConflictDetectionTimer>
|
||||
</span>
|
||||
{
|
||||
{
|
||||
Expired: __( 'Timer expired', 'font-awesome' ),
|
||||
Stopped: __( 'Timer stopped', 'font-awesome' ),
|
||||
Expired: __('Timer expired', 'font-awesome'),
|
||||
Stopped: __('Timer stopped', 'font-awesome'),
|
||||
Restarting: null
|
||||
}[runStatus.code]
|
||||
}
|
||||
|
@ -354,4 +409,4 @@ function ConflictDetectionReporter() {
|
|||
)
|
||||
}
|
||||
|
||||
export default withErrorBoundary( ConflictDetectionReporter )
|
||||
export default withErrorBoundary(ConflictDetectionReporter)
|
||||
|
|
|
@ -11,15 +11,15 @@ import createInterpolateElement from './createInterpolateElement'
|
|||
|
||||
export default function ConflictDetectionScannerSection() {
|
||||
const dispatch = useDispatch()
|
||||
const detectConflictsUntil = useSelector(state => state.detectConflictsUntil)
|
||||
const nowMs = (new Date()).valueOf()
|
||||
const detectingConflicts = (new Date(detectConflictsUntil * 1000)) > nowMs
|
||||
const { isSubmitting, hasSubmitted, message, success } = useSelector(state => state.conflictDetectionScannerStatus)
|
||||
const showConflictDetectionReporter = useSelector(state => state.showConflictDetectionReporter)
|
||||
const detectConflictsUntil = useSelector((state) => state.detectConflictsUntil)
|
||||
const nowMs = new Date().valueOf()
|
||||
const detectingConflicts = new Date(detectConflictsUntil * 1000) > nowMs
|
||||
const { isSubmitting, hasSubmitted, message, success } = useSelector((state) => state.conflictDetectionScannerStatus)
|
||||
const showConflictDetectionReporter = useSelector((state) => state.showConflictDetectionReporter)
|
||||
const store = useStore()
|
||||
|
||||
useEffect(() => {
|
||||
if(showConflictDetectionReporter && !isConflictDetectionReporterMounted()) {
|
||||
if (showConflictDetectionReporter && !isConflictDetectionReporterMounted()) {
|
||||
// We are not setting up the reporting hook, because the conflict scanner
|
||||
// script is not actually going to run when it's initially activated from
|
||||
// this view. The conflict scanner box will appear to alert the user,
|
||||
|
@ -29,53 +29,66 @@ export default function ConflictDetectionScannerSection() {
|
|||
|
||||
mountConflictDetectionReporter(store)
|
||||
}
|
||||
}, [ showConflictDetectionReporter, store ])
|
||||
}, [showConflictDetectionReporter, store])
|
||||
|
||||
return <div>
|
||||
<h2 className={ sharedStyles['section-title'] }>{ __( 'Detect Conflicts with Other Versions of Font Awesome', 'font-awesome' ) }</h2>
|
||||
<div className={sharedStyles['explanation']}>
|
||||
<p>
|
||||
{ __( 'If you are having trouble loading Font Awesome icons on your WordPress site, it may be because other themes or plugins are loading conflicting versions of Font Awesome. You can use our conflict scanner to detect other versions of Font Awesome running on your site.', 'font-awesome' ) }
|
||||
</p>
|
||||
return (
|
||||
<div>
|
||||
<h2 className={sharedStyles['section-title']}>{__('Detect Conflicts with Other Versions of Font Awesome', 'font-awesome')}</h2>
|
||||
<div className={sharedStyles['explanation']}>
|
||||
<p>
|
||||
{__(
|
||||
'If you are having trouble loading Font Awesome icons on your WordPress site, it may be because other themes or plugins are loading conflicting versions of Font Awesome. You can use our conflict scanner to detect other versions of Font Awesome running on your site.',
|
||||
'font-awesome'
|
||||
)}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{
|
||||
createInterpolateElement(
|
||||
__( 'Enable the scanner below and a box will appear in the bottom corner of your window while it runs for 10 minutes (only you and other admins can see the box). While the scanner is running, browse your site, especially the pages having trouble to catch any <noWrap>Slimers - *ahem* - conflicts</noWrap> in the scanner.', 'font-awesome' ),
|
||||
<p>
|
||||
{createInterpolateElement(
|
||||
__(
|
||||
'Enable the scanner below and a box will appear in the bottom corner of your window while it runs for 10 minutes (only you and other admins can see the box). While the scanner is running, browse your site, especially the pages having trouble to catch any <noWrap>Slimers - *ahem* - conflicts</noWrap> in the scanner.',
|
||||
'font-awesome'
|
||||
),
|
||||
{
|
||||
noWrap: <span style={{ whiteSpace: "nowrap" }} />
|
||||
noWrap: <span style={{ whiteSpace: 'nowrap' }} />
|
||||
}
|
||||
)
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<div className={sharedStyles['scanner-actions']}>
|
||||
{
|
||||
detectingConflicts
|
||||
? <button className={sharedStyles['faPrimary']} disabled >
|
||||
{ __( 'Scanner running', 'font-awesome' ) }: <ConflictDetectionTimer />
|
||||
</button>
|
||||
: <button className="button button-primary" disabled={ isSubmitting } onClick={() => dispatch(setConflictDetectionScanner({ enable: true }))}>
|
||||
{
|
||||
sprintf(
|
||||
__( 'Enable scanner for %d minutes', 'font-awesome' ),
|
||||
CONFLICT_DETECTION_SCANNER_DURATION_MIN
|
||||
)
|
||||
}
|
||||
</button>
|
||||
}
|
||||
<div className={sharedStyles['scanner-runstatus']}>
|
||||
{
|
||||
isSubmitting
|
||||
? <FontAwesomeIcon icon={ faSpinner } spin />
|
||||
: hasSubmitted
|
||||
? success
|
||||
? <FontAwesomeIcon icon={ faCheck } />
|
||||
: <><FontAwesomeIcon icon={ faSkull } /> <span>{ message }</span></>
|
||||
: null
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className={sharedStyles['scanner-actions']}>
|
||||
{detectingConflicts ? (
|
||||
<button
|
||||
className={sharedStyles['faPrimary']}
|
||||
disabled
|
||||
>
|
||||
{__('Scanner running', 'font-awesome')}: <ConflictDetectionTimer />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="button button-primary"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => dispatch(setConflictDetectionScanner({ enable: true }))}
|
||||
>
|
||||
{sprintf(__('Enable scanner for %d minutes', 'font-awesome'), CONFLICT_DETECTION_SCANNER_DURATION_MIN)}
|
||||
</button>
|
||||
)}
|
||||
<div className={sharedStyles['scanner-runstatus']}>
|
||||
{isSubmitting ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinner}
|
||||
spin
|
||||
/>
|
||||
) : hasSubmitted ? (
|
||||
success ? (
|
||||
<FontAwesomeIcon icon={faCheck} />
|
||||
) : (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faSkull} /> <span>{message}</span>
|
||||
</>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<hr className={sharedStyles['section-divider']} />
|
||||
</div>
|
||||
<hr className={ sharedStyles['section-divider'] }/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@ import React, { useState, useEffect } from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import sharedStyles from './App.module.css'
|
||||
import padStart from 'lodash/padStart'
|
||||
import dropWhile from 'lodash/dropWhile'
|
||||
import { padStart, dropWhile } from 'lodash'
|
||||
import { __, sprintf } from '@wordpress/i18n'
|
||||
|
||||
const SECONDS_PER_DAY = 60 * 60 * 24
|
||||
|
@ -12,39 +11,39 @@ const SECONDS_PER_MINUTE = 60
|
|||
|
||||
export function timerString(durationSeconds) {
|
||||
const days = Math.floor(durationSeconds / SECONDS_PER_DAY)
|
||||
const hours = Math.floor((durationSeconds - (days * SECONDS_PER_DAY)) / SECONDS_PER_HOUR)
|
||||
const hours = Math.floor((durationSeconds - days * SECONDS_PER_DAY) / SECONDS_PER_HOUR)
|
||||
const minutes = Math.floor((durationSeconds - (days * SECONDS_PER_DAY + hours * SECONDS_PER_HOUR)) / SECONDS_PER_MINUTE)
|
||||
const seconds = durationSeconds - (days * SECONDS_PER_DAY + hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE)
|
||||
|
||||
return dropWhile(
|
||||
[days, hours, minutes, seconds].reduce((acc, unit, index) => {
|
||||
if(0 === index && unit !== 0){
|
||||
if (0 === index && unit !== 0) {
|
||||
acc.push(unit.toString())
|
||||
} else {
|
||||
acc.push(padStart(unit.toString(), 2, '0'))
|
||||
}
|
||||
return acc
|
||||
}, []),
|
||||
part => part.match(/^[0]+$/)
|
||||
(part) => part.match(/^[0]+$/)
|
||||
).join(':')
|
||||
}
|
||||
|
||||
function secondsRemaining(endTime) {
|
||||
const now = Math.floor((new Date()) / 1000)
|
||||
const now = Math.floor(new Date() / 1000)
|
||||
const remaining = endTime - now
|
||||
|
||||
return remaining < 0 ? 0 : remaining
|
||||
}
|
||||
|
||||
export default function ConflictDetectionTimer({ addDescription, children }) {
|
||||
const detectConflictsUntil = useSelector(state => state.detectConflictsUntil)
|
||||
const detectConflictsUntil = useSelector((state) => state.detectConflictsUntil)
|
||||
const [timeRemaining, setTimer] = useState(secondsRemaining(detectConflictsUntil))
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
let timeoutId = null
|
||||
|
||||
if(secondsRemaining(detectConflictsUntil) > 0) {
|
||||
if (secondsRemaining(detectConflictsUntil) > 0) {
|
||||
timeoutId = setTimeout(() => setTimer(secondsRemaining(detectConflictsUntil)), 1000)
|
||||
} else {
|
||||
setTimer(timerString(0))
|
||||
|
@ -53,25 +52,21 @@ export default function ConflictDetectionTimer({ addDescription, children }) {
|
|||
})
|
||||
}
|
||||
|
||||
return () => timeoutId && clearTimeout( timeoutId )
|
||||
return () => timeoutId && clearTimeout(timeoutId)
|
||||
}, [detectConflictsUntil, timeRemaining, dispatch])
|
||||
|
||||
return timeRemaining <= 0 ? null : <span className={ sharedStyles['conflict-detection-timer'] }>
|
||||
{ timerString( timeRemaining ) }
|
||||
{
|
||||
!!addDescription &&
|
||||
(
|
||||
timeRemaining > 60
|
||||
/* translators: 1: space */
|
||||
? sprintf( __( '%1$sminutes left to browse your site for trouble', 'font-awesome' ), ' ' )
|
||||
/* translators: 1: space */
|
||||
: sprintf( __( '%1$sseconds left to browse your site for trouble', 'font-awesome' ), ' ' )
|
||||
)
|
||||
}
|
||||
{
|
||||
children
|
||||
}
|
||||
return timeRemaining <= 0 ? null : (
|
||||
<span className={sharedStyles['conflict-detection-timer']}>
|
||||
{timerString(timeRemaining)}
|
||||
{!!addDescription &&
|
||||
(timeRemaining > 60
|
||||
? /* translators: 1: space */
|
||||
sprintf(__('%1$sminutes left to browse your site for trouble', 'font-awesome'), ' ')
|
||||
: /* translators: 1: space */
|
||||
sprintf(__('%1$sseconds left to browse your site for trouble', 'font-awesome'), ' '))}
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
ConflictDetectionTimer.propTypes = {
|
||||
|
|
|
@ -13,10 +13,10 @@ class ErrorBoundary extends React.Component {
|
|||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.group(ERROR_REPORT_PREAMBLE)
|
||||
console.log( error )
|
||||
console.log( errorInfo )
|
||||
console.log(error)
|
||||
console.log(errorInfo)
|
||||
console.groupEnd()
|
||||
this.setState({error, errorInfo})
|
||||
this.setState({ error, errorInfo })
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -3,18 +3,17 @@ import styles from './ErrorFallbackView.module.css'
|
|||
import Alert from './Alert'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
|
||||
export const fatalAlert = <Alert title={ __( 'Whoops, this is embarrassing', 'font-awesome' ) } type='warning'>
|
||||
<p>
|
||||
{
|
||||
__( 'Some unexpected error has occurred. There might be some additional diagnostic information in the JavaScript console.', 'font-awesome' )
|
||||
}
|
||||
</p>
|
||||
</Alert>
|
||||
export const fatalAlert = (
|
||||
<Alert
|
||||
title={__('Whoops, this is embarrassing', 'font-awesome')}
|
||||
type="warning"
|
||||
>
|
||||
<p>{__('Some unexpected error has occurred. There might be some additional diagnostic information in the JavaScript console.', 'font-awesome')}</p>
|
||||
</Alert>
|
||||
)
|
||||
|
||||
function ErrorFallbackView() {
|
||||
return <div className={ styles['error-fallback'] }>
|
||||
{ fatalAlert }
|
||||
</div>
|
||||
return <div className={styles['error-fallback']}>{fatalAlert}</div>
|
||||
}
|
||||
|
||||
export default ErrorFallbackView
|
||||
|
|
|
@ -9,30 +9,30 @@ import { setActiveAdminTab } from './store/actions'
|
|||
import { __ } from '@wordpress/i18n'
|
||||
|
||||
export default function FontAwesomeAdminView() {
|
||||
const activeAdminTab = useSelector(state => state.activeAdminTab || ADMIN_TAB_SETTINGS )
|
||||
const activeAdminTab = useSelector((state) => state.activeAdminTab || ADMIN_TAB_SETTINGS)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return (
|
||||
<div className={ classnames(styles['font-awesome-admin-view']) }>
|
||||
return (
|
||||
<div className={classnames(styles['font-awesome-admin-view'])}>
|
||||
<h1>Font Awesome</h1>
|
||||
<div className={styles['tab-header']}>
|
||||
<button
|
||||
<button
|
||||
onClick={() => dispatch(setActiveAdminTab(ADMIN_TAB_SETTINGS))}
|
||||
disabled={ activeAdminTab === ADMIN_TAB_SETTINGS }
|
||||
disabled={activeAdminTab === ADMIN_TAB_SETTINGS}
|
||||
>
|
||||
{ __( 'Settings', 'font-awesome' ) }
|
||||
{__('Settings', 'font-awesome')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => dispatch(setActiveAdminTab(ADMIN_TAB_TROUBLESHOOT))}
|
||||
disabled={ activeAdminTab === ADMIN_TAB_TROUBLESHOOT }
|
||||
disabled={activeAdminTab === ADMIN_TAB_TROUBLESHOOT}
|
||||
>
|
||||
{ __( 'Troubleshoot', 'font-awesome' ) }
|
||||
{__('Troubleshoot', 'font-awesome')}
|
||||
</button>
|
||||
</div>
|
||||
{
|
||||
{
|
||||
[ADMIN_TAB_SETTINGS]: <SettingsTab/>,
|
||||
[ADMIN_TAB_TROUBLESHOOT]: <TroubleshootTab/>
|
||||
[ADMIN_TAB_SETTINGS]: <SettingsTab />,
|
||||
[ADMIN_TAB_TROUBLESHOOT]: <TroubleshootTab />
|
||||
}[activeAdminTab]
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import React from 'react'
|
||||
import { mount } from 'enzyme'
|
||||
import { Provider } from 'react-redux'
|
||||
import { createStore } from './store'
|
||||
import ErrorBoundary from './ErrorBoundary'
|
||||
import FontAwesomeAdminView from './FontAwesomeAdminView'
|
||||
|
||||
describe('FontAwesomeAdminView', () => {
|
||||
test('mounts successfully', () => {
|
||||
const initialData = JSON.parse(
|
||||
'{"apiNonce":"deadbeef42","apiUrl":"http://localhost:8765/index.php?rest_route=/font-awesome/v1","unregisteredClients":[],"showConflictDetectionReporter":"","settingsPageUrl":"http://localhost:8765/wp-admin/options-general.php?page=font-awesome","detectConflictsUntil":0,"options":{"usePro":false,"compat":true,"technology":"webfont","pseudoElements":false,"version":"5.12.0"},"showAdmin":"1","onSettingsPage":"1","clientPreferences":{"beta-plugin":{"name":"beta-plugin","compat":true}},"releases":{"available":["5.12.0","5.11.2","5.11.1","5.11.0","5.10.2","5.10.1","5.10.0","5.9.0","5.8.2","5.8.1","5.8.0","5.7.2","5.7.1","5.7.0","5.6.3","5.6.1","5.6.0","5.5.0","5.4.2","5.4.1","5.3.1","5.2.0","5.1.1","5.1.0","5.0.13","5.0.12","5.0.10","5.0.9","5.0.8","5.0.6","5.0.4","5.0.3","5.0.2","5.0.1"],"latest_version_5":"5.12.0","latest_version_6":"6.1.1"},"pluginVersion":"4.0.0-rc13","preferenceConflicts":[],"v3DeprecationWarning":""}'
|
||||
)
|
||||
|
||||
global['__FontAwesomeOfficialPlugin__'] = initialData
|
||||
|
||||
const store = createStore(initialData)
|
||||
|
||||
const wrapper = mount(
|
||||
<ErrorBoundary>
|
||||
<Provider store={ store }>
|
||||
<FontAwesomeAdminView/>
|
||||
</Provider>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
|
||||
expect(wrapper).toBeTruthy()
|
||||
})
|
||||
})
|
|
@ -5,115 +5,109 @@ import styles from './KitSelectView.module.css'
|
|||
import PropTypes from 'prop-types'
|
||||
import { useSelector } from 'react-redux'
|
||||
import Alert from './Alert'
|
||||
import get from 'lodash/get'
|
||||
import has from 'lodash/has'
|
||||
import size from 'lodash/size'
|
||||
import { get, has, size } from 'lodash'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import createInterpolateElement from './createInterpolateElement'
|
||||
|
||||
export default function KitConfigView({ kitToken }) {
|
||||
const kitTokenIsActive = useSelector(state => get(state, 'options.kitToken') === kitToken)
|
||||
const kitTokenApiData = useSelector(state => (state.kits || []).find(k => k.token === kitToken))
|
||||
const pendingOptionConflicts = useSelector(state => state.pendingOptionConflicts)
|
||||
const hasChecked = useSelector(state => state.preferenceConflictDetection.hasChecked)
|
||||
const preferenceCheckSuccess = useSelector(state => state.preferenceConflictDetection.success)
|
||||
const kitTokenIsActive = useSelector((state) => get(state, 'options.kitToken') === kitToken)
|
||||
const kitTokenApiData = useSelector((state) => (state.kits || []).find((k) => k.token === kitToken))
|
||||
const pendingOptionConflicts = useSelector((state) => state.pendingOptionConflicts)
|
||||
const hasChecked = useSelector((state) => state.preferenceConflictDetection.hasChecked)
|
||||
const preferenceCheckSuccess = useSelector((state) => state.preferenceConflictDetection.success)
|
||||
|
||||
const technology = useSelector(state =>
|
||||
kitTokenIsActive
|
||||
? state.options.technology
|
||||
: kitTokenApiData.technologySelected === 'svg'
|
||||
? 'svg'
|
||||
: 'webfont'
|
||||
)
|
||||
const technology = useSelector((state) => (kitTokenIsActive ? state.options.technology : kitTokenApiData.technologySelected === 'svg' ? 'svg' : 'webfont'))
|
||||
|
||||
const usePro = useSelector(state =>
|
||||
kitTokenIsActive
|
||||
? state.options.usePro
|
||||
: kitTokenApiData.licenseSelected === 'pro'
|
||||
)
|
||||
const usePro = useSelector((state) => (kitTokenIsActive ? state.options.usePro : kitTokenApiData.licenseSelected === 'pro'))
|
||||
|
||||
const compat = useSelector(state =>
|
||||
kitTokenIsActive
|
||||
? state.options.compat
|
||||
: kitTokenApiData.shimEnabled
|
||||
)
|
||||
const compat = useSelector((state) => (kitTokenIsActive ? state.options.compat : kitTokenApiData.shimEnabled))
|
||||
|
||||
const version = useSelector(state =>
|
||||
kitTokenIsActive
|
||||
? state.options.version
|
||||
: kitTokenApiData.version
|
||||
)
|
||||
const version = useSelector((state) => (kitTokenIsActive ? state.options.version : kitTokenApiData.version))
|
||||
|
||||
function getDetectionStatusForOption(option) {
|
||||
if ( hasChecked && preferenceCheckSuccess && has(pendingOptionConflicts, option) ) {
|
||||
return <Alert title={ __( 'Preference Conflict', 'font-awesome' ) } type='warning'>
|
||||
{
|
||||
size(pendingOptionConflicts[option]) > 1
|
||||
? <div>
|
||||
{ __( 'This change might cause problems for these themes or plugins:', 'font-awesome' ) } { pendingOptionConflicts[option].join(', ') }.
|
||||
</div>
|
||||
: <div>
|
||||
{ __( 'This change might cause problems for the theme or plugin:', 'font-awesome' ) } { pendingOptionConflicts[option][0] }.
|
||||
if (hasChecked && preferenceCheckSuccess && has(pendingOptionConflicts, option)) {
|
||||
return (
|
||||
<Alert
|
||||
title={__('Preference Conflict', 'font-awesome')}
|
||||
type="warning"
|
||||
>
|
||||
{size(pendingOptionConflicts[option]) > 1 ? (
|
||||
<div>
|
||||
{__('This change might cause problems for these themes or plugins:', 'font-awesome')} {pendingOptionConflicts[option].join(', ')}.
|
||||
</div>
|
||||
}
|
||||
</Alert>
|
||||
) : (
|
||||
<div>
|
||||
{__('This change might cause problems for the theme or plugin:', 'font-awesome')} {pendingOptionConflicts[option][0]}.
|
||||
</div>
|
||||
)}
|
||||
</Alert>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (!kitTokenIsActive && !kitTokenApiData)
|
||||
? <Alert type="warning" title={ __('Oh no! We could not find the kit data for the selected kit token.', 'font-awesome' )}>
|
||||
{
|
||||
__( 'Try reloading.', 'font-awesome' )
|
||||
}
|
||||
</Alert>
|
||||
: <div className={ styles['kit-config-view-container'] }>
|
||||
<table className={ styles['selected-kit-settings'] }>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className={ styles['label'] }>{ __( 'Icons', 'font-awesome' ) }</th>
|
||||
<td className={ styles['value'] }>
|
||||
{ usePro ? 'Pro' : 'Free' }
|
||||
{ getDetectionStatusForOption('usePro') }
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={ styles['label'] }>{ __( 'Technology', 'font-awesome' ) }</th>
|
||||
<td className={ styles['value'] }>
|
||||
{ technology }
|
||||
{ getDetectionStatusForOption('technology') }
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={ styles['label'] }>{ __( 'Version', 'font-awesome' ) }</th>
|
||||
<td className={ styles['value'] }>
|
||||
{ version }
|
||||
{ getDetectionStatusForOption('version') }
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={ styles['label'] }>{ __( 'Older Version Compatibility', 'font-awesome' ) }</th>
|
||||
<td className={ styles['value'] }>
|
||||
{ compat ? 'On' : 'Off' }
|
||||
{ getDetectionStatusForOption('compat') }
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p className={ styles['tip-text'] }>
|
||||
{
|
||||
createInterpolateElement(
|
||||
__( 'Make changes on <a>fontawesome.com/kits <externalLinkIcon/></a>', 'font-awesome' ),
|
||||
{
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
a: <a target="_blank" rel="noopener noreferrer" href="https://fontawesome.com/kits" />,
|
||||
externalLinkIcon: <FontAwesomeIcon icon={faExternalLinkAlt} style={{marginLeft: '.5em'}} />
|
||||
}
|
||||
)
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
return !kitTokenIsActive && !kitTokenApiData ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
title={__('Oh no! We could not find the kit data for the selected kit token.', 'font-awesome')}
|
||||
>
|
||||
{__('Try reloading.', 'font-awesome')}
|
||||
</Alert>
|
||||
) : (
|
||||
<div className={styles['kit-config-view-container']}>
|
||||
<table className={styles['selected-kit-settings']}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className={styles['label']}>{__('Icons', 'font-awesome')}</th>
|
||||
<td className={styles['value']}>
|
||||
{usePro ? 'Pro' : 'Free'}
|
||||
{getDetectionStatusForOption('usePro')}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={styles['label']}>{__('Technology', 'font-awesome')}</th>
|
||||
<td className={styles['value']}>
|
||||
{technology}
|
||||
{getDetectionStatusForOption('technology')}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={styles['label']}>{__('Version', 'font-awesome')}</th>
|
||||
<td className={styles['value']}>
|
||||
{version}
|
||||
{getDetectionStatusForOption('version')}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={styles['label']}>{__('Older Version Compatibility', 'font-awesome')}</th>
|
||||
<td className={styles['value']}>
|
||||
{compat ? 'On' : 'Off'}
|
||||
{getDetectionStatusForOption('compat')}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p className={styles['tip-text']}>
|
||||
{createInterpolateElement(__('Make changes on <a>fontawesome.com/kits <externalLinkIcon/></a>', 'font-awesome'), {
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
a: (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://fontawesome.com/kits"
|
||||
/>
|
||||
),
|
||||
externalLinkIcon: (
|
||||
<FontAwesomeIcon
|
||||
icon={faExternalLinkAlt}
|
||||
style={{ marginLeft: '.5em' }}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
KitConfigView.propTypes = {
|
||||
|
|
|
@ -1,50 +1,37 @@
|
|||
import React, { createRef, useState, useEffect } from 'react'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Alert from './Alert'
|
||||
import {
|
||||
resetPendingOptions,
|
||||
queryKits,
|
||||
addPendingOption,
|
||||
checkPreferenceConflicts,
|
||||
updateApiToken,
|
||||
resetOptionsFormState
|
||||
} from './store/actions'
|
||||
import { resetPendingOptions, queryKits, addPendingOption, checkPreferenceConflicts, updateApiToken, resetOptionsFormState } from './store/actions'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faSpinner,
|
||||
faSync,
|
||||
faExternalLinkAlt,
|
||||
faRedo,
|
||||
faSkull,
|
||||
faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faSpinner, faSync, faExternalLinkAlt, faRedo, faSkull, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faQuestionCircle, faCheckCircle } from '@fortawesome/free-regular-svg-icons'
|
||||
import styles from './KitSelectView.module.css'
|
||||
import sharedStyles from './App.module.css'
|
||||
import classnames from 'classnames'
|
||||
import PropTypes from 'prop-types'
|
||||
import size from 'lodash/size'
|
||||
import { size } from 'lodash'
|
||||
import { sprintf, __ } from '@wordpress/i18n'
|
||||
|
||||
export default function KitSelectView({ useOption, masterSubmitButtonShowing, setMasterSubmitButtonShowing }) {
|
||||
const dispatch = useDispatch()
|
||||
const kitTokenActive = useSelector(state => state.options.kitToken)
|
||||
const kitTokenActive = useSelector((state) => state.options.kitToken)
|
||||
const kitToken = useOption('kitToken')
|
||||
const [ pendingApiToken, setPendingApiToken ] = useState(null)
|
||||
const [ showingRemoveApiTokenAlert, setShowRemoveApiTokenAlert ] = useState(false)
|
||||
const [ showApiTokenInputForUpdate, setShowApiTokenInputForUpdate ] = useState(false)
|
||||
const apiToken = useSelector(state => {
|
||||
if( null !== pendingApiToken ) return pendingApiToken
|
||||
const [pendingApiToken, setPendingApiToken] = useState(null)
|
||||
const [showingRemoveApiTokenAlert, setShowRemoveApiTokenAlert] = useState(false)
|
||||
const [showApiTokenInputForUpdate, setShowApiTokenInputForUpdate] = useState(false)
|
||||
const apiToken = useSelector((state) => {
|
||||
if (null !== pendingApiToken) return pendingApiToken
|
||||
|
||||
return state.options.apiToken
|
||||
})
|
||||
const kits = useSelector( state => state.kits ) || []
|
||||
const hasSubmitted = useSelector(state => state.optionsFormState.hasSubmitted)
|
||||
const submitSuccess = useSelector(state => state.optionsFormState.success)
|
||||
const submitMessage = useSelector(state => state.optionsFormState.message)
|
||||
const isSubmitting = useSelector(state => state.optionsFormState.isSubmitting)
|
||||
const kits = useSelector((state) => state.kits) || []
|
||||
const hasSubmitted = useSelector((state) => state.optionsFormState.hasSubmitted)
|
||||
const submitSuccess = useSelector((state) => state.optionsFormState.success)
|
||||
const submitMessage = useSelector((state) => state.optionsFormState.message)
|
||||
const isSubmitting = useSelector((state) => state.optionsFormState.isSubmitting)
|
||||
|
||||
function removeApiToken() {
|
||||
if( !!kitTokenActive ) {
|
||||
if (!!kitTokenActive) {
|
||||
setShowRemoveApiTokenAlert(true)
|
||||
} else {
|
||||
dispatch(updateApiToken({ apiToken: false }))
|
||||
|
@ -59,44 +46,41 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
|||
* with registered clients.
|
||||
*/
|
||||
function handleKitChange({ kitToken }) {
|
||||
if('' === kitToken) {
|
||||
if ('' === kitToken) {
|
||||
// You can't select a non-kit option. The empty option only
|
||||
// appears in the selection dropdown as a placeholder before a kit is
|
||||
// selected
|
||||
return
|
||||
}
|
||||
|
||||
const selectedKit = (kits || []).find(k => k.token === kitToken)
|
||||
const selectedKit = (kits || []).find((k) => k.token === kitToken)
|
||||
|
||||
if( !selectedKit ) {
|
||||
throw new Error(
|
||||
sprintf(
|
||||
__( 'When selecting to use kit %s, somehow the information we needed was missing. Try reloading the page.' ),
|
||||
kitToken
|
||||
)
|
||||
)
|
||||
if (!selectedKit) {
|
||||
throw new Error(sprintf(__('When selecting to use kit %s, somehow the information we needed was missing. Try reloading the page.'), kitToken))
|
||||
}
|
||||
|
||||
if( kitTokenActive === kitToken ) {
|
||||
if (kitTokenActive === kitToken) {
|
||||
// We're just resetting back to the state we were in
|
||||
dispatch(resetPendingOptions())
|
||||
} else {
|
||||
dispatch(addPendingOption({
|
||||
kitToken,
|
||||
technology: 'svg' === selectedKit.technologySelected ? 'svg' : 'webfont',
|
||||
usePro: 'pro' === selectedKit.licenseSelected,
|
||||
compat: selectedKit.shimEnabled,
|
||||
version: selectedKit.version,
|
||||
// At the time this is being implemented, kits don't yet support
|
||||
// toggling pseudoElement support for SVG, but it's implicitly supported for webfont.
|
||||
pseudoElements: 'svg' !== selectedKit.technologySelected
|
||||
}))
|
||||
dispatch(
|
||||
addPendingOption({
|
||||
kitToken,
|
||||
technology: 'svg' === selectedKit.technologySelected ? 'svg' : 'webfont',
|
||||
usePro: 'pro' === selectedKit.licenseSelected,
|
||||
compat: selectedKit.shimEnabled,
|
||||
version: selectedKit.version,
|
||||
// At the time this is being implemented, kits don't yet support
|
||||
// toggling pseudoElement support for SVG, but it's implicitly supported for webfont.
|
||||
pseudoElements: 'svg' !== selectedKit.technologySelected
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
dispatch(checkPreferenceConflicts())
|
||||
}
|
||||
|
||||
const kitsQueryStatus = useSelector(state => state.kitsQueryStatus)
|
||||
const kitsQueryStatus = useSelector((state) => state.kitsQueryStatus)
|
||||
|
||||
/**
|
||||
* This seems like a lot of effort just to keep the focus on the API Token input
|
||||
|
@ -111,15 +95,14 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
|||
* button, or pressing the tab key, for example.
|
||||
*/
|
||||
const apiTokenInputRef = createRef()
|
||||
const [ apiTokenInputHasFocus, setApiTokenInputHasFocus ] = useState( false )
|
||||
const [apiTokenInputHasFocus, setApiTokenInputHasFocus] = useState(false)
|
||||
useEffect(() => {
|
||||
if( !!apiTokenInputRef.current && apiTokenInputHasFocus ) {
|
||||
if (!!apiTokenInputRef.current && apiTokenInputHasFocus) {
|
||||
apiTokenInputRef.current.focus()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const hasSavedApiToken = useSelector(state => !! state.options.apiToken)
|
||||
const hasSavedApiToken = useSelector((state) => !!state.options.apiToken)
|
||||
|
||||
function cancelApiTokenUpdate() {
|
||||
setShowApiTokenInputForUpdate(false)
|
||||
|
@ -128,78 +111,101 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
|||
}
|
||||
|
||||
function ApiTokenInput() {
|
||||
|
||||
useEffect(() => {
|
||||
if ( submitSuccess && showApiTokenInputForUpdate ) {
|
||||
if (submitSuccess && showApiTokenInputForUpdate) {
|
||||
setShowApiTokenInputForUpdate(false)
|
||||
setMasterSubmitButtonShowing(true)
|
||||
}
|
||||
} )
|
||||
})
|
||||
|
||||
return <>
|
||||
<div className={ classnames( styles['field-apitoken'], { [styles['api-token-update']]: showApiTokenInputForUpdate } )}>
|
||||
<label htmlFor="api_token">
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faQuestionCircle } size="lg" />
|
||||
{ __( 'API Token', 'font-awesome' ) }
|
||||
</label>
|
||||
<div>
|
||||
<input
|
||||
id="api_token"
|
||||
name="api_token"
|
||||
type="text"
|
||||
ref={ apiTokenInputRef }
|
||||
value={ pendingApiToken || '' }
|
||||
size="20"
|
||||
onChange={ e => {
|
||||
setApiTokenInputHasFocus( true )
|
||||
setPendingApiToken(e.target.value)
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
<div className={classnames(styles['field-apitoken'], { [styles['api-token-update']]: showApiTokenInputForUpdate })}>
|
||||
<label htmlFor="api_token">
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faQuestionCircle}
|
||||
size="lg"
|
||||
/>
|
||||
{__('API Token', 'font-awesome')}
|
||||
</label>
|
||||
<div>
|
||||
<input
|
||||
id="api_token"
|
||||
name="api_token"
|
||||
type="text"
|
||||
ref={apiTokenInputRef}
|
||||
value={pendingApiToken || ''}
|
||||
size="20"
|
||||
onChange={(e) => {
|
||||
setApiTokenInputHasFocus(true)
|
||||
setPendingApiToken(e.target.value)
|
||||
}}
|
||||
/>
|
||||
|
||||
<p>
|
||||
{ __( 'Grab your secure and unique API token from your Font Awesome account page and enter it here so we can securely fetch your kits.', 'font-awesome') } <a target="_blank" rel="noopener noreferrer" href="https://fontawesome.com/account#api-tokens">
|
||||
{ __( 'Get your API token on fontawesome.com', 'font-awesome') } <FontAwesomeIcon icon={faExternalLinkAlt} style={{marginLeft: '.5em'}} />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
{__(
|
||||
'Grab your secure and unique API token from your Font Awesome account page and enter it here so we can securely fetch your kits.',
|
||||
'font-awesome'
|
||||
)}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://fontawesome.com/account#api-tokens"
|
||||
>
|
||||
{__('Get your API token on fontawesome.com', 'font-awesome')}{' '}
|
||||
<FontAwesomeIcon
|
||||
icon={faExternalLinkAlt}
|
||||
style={{ marginLeft: '.5em' }}
|
||||
/>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="submit">
|
||||
<input
|
||||
type="submit"
|
||||
name="submit"
|
||||
id="submit"
|
||||
className="button button-primary"
|
||||
value={ __( 'Save API Token', 'font-awesome' ) }
|
||||
disabled={ !pendingApiToken }
|
||||
onMouseDown={ () => {
|
||||
<div className="submit">
|
||||
<input
|
||||
type="submit"
|
||||
name="submit"
|
||||
id="submit"
|
||||
className="button button-primary"
|
||||
value={__('Save API Token', 'font-awesome')}
|
||||
disabled={!pendingApiToken}
|
||||
onMouseDown={() => {
|
||||
dispatch(updateApiToken({ apiToken: pendingApiToken, runQueryKits: true }))
|
||||
setPendingApiToken(null)
|
||||
}
|
||||
}
|
||||
/>
|
||||
{
|
||||
(hasSubmitted && ! submitSuccess) &&
|
||||
<div className={ classnames(sharedStyles['submit-status'], sharedStyles['fail']) }>
|
||||
<div className={ classnames(sharedStyles['fail-icon-container']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSkull } />
|
||||
}}
|
||||
/>
|
||||
{hasSubmitted && !submitSuccess && (
|
||||
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
||||
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faSkull}
|
||||
/>
|
||||
</div>
|
||||
<div className={sharedStyles['explanation']}>{submitMessage}</div>
|
||||
</div>
|
||||
<div className={ sharedStyles['explanation'] }>
|
||||
{ submitMessage }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
isSubmitting &&
|
||||
<span className={ classnames(sharedStyles['submit-status'], sharedStyles['submitting']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={faSpinner} spin/>
|
||||
</span>
|
||||
}
|
||||
{
|
||||
(showApiTokenInputForUpdate && ! isSubmitting) &&
|
||||
<button onClick={ () => cancelApiTokenUpdate() } className={ styles['button-dismissable'] }>{ __('Nevermind', 'font-awesome') }</button>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isSubmitting && (
|
||||
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faSpinner}
|
||||
spin
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
{showApiTokenInputForUpdate && !isSubmitting && (
|
||||
<button
|
||||
onClick={() => cancelApiTokenUpdate()}
|
||||
className={styles['button-dismissable']}
|
||||
>
|
||||
{__('Nevermind', 'font-awesome')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function ApiTokenControl() {
|
||||
|
@ -210,40 +216,67 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
|||
setShowRemoveApiTokenAlert(false)
|
||||
}
|
||||
|
||||
return <div className={ styles['api-token-control-wrapper'] }>
|
||||
<div className={ classnames( styles['api-token-control'], { [styles['api-token-update']]: showApiTokenInputForUpdate } )}>
|
||||
{
|
||||
showApiTokenInputForUpdate
|
||||
? <ApiTokenInput />
|
||||
: <>
|
||||
<p className={ styles['token-saved'] }>
|
||||
return (
|
||||
<div className={styles['api-token-control-wrapper']}>
|
||||
<div className={classnames(styles['api-token-control'], { [styles['api-token-update']]: showApiTokenInputForUpdate })}>
|
||||
{showApiTokenInputForUpdate ? (
|
||||
<ApiTokenInput />
|
||||
) : (
|
||||
<>
|
||||
<p className={styles['token-saved']}>
|
||||
<span>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faCheckCircle } size="lg" />
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faCheckCircle}
|
||||
size="lg"
|
||||
/>
|
||||
</span>
|
||||
{ __( 'API Token Saved', 'font-awesome' ) }
|
||||
{__('API Token Saved', 'font-awesome')}
|
||||
</p>
|
||||
{
|
||||
!!apiToken &&
|
||||
<div className={ styles['button-group'] }>
|
||||
<button onClick={ () => switchToApiTokenUpdate() } className={ styles['refresh'] } type="button">
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSync } title="update" alt="update" />
|
||||
<span>{ __( 'Update token', 'font-awesome' ) }</span>
|
||||
{!!apiToken && (
|
||||
<div className={styles['button-group']}>
|
||||
<button
|
||||
onClick={() => switchToApiTokenUpdate()}
|
||||
className={styles['refresh']}
|
||||
type="button"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faSync}
|
||||
title="update"
|
||||
alt="update"
|
||||
/>
|
||||
<span>{__('Update token', 'font-awesome')}</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => removeApiToken()}
|
||||
className={styles['remove']}
|
||||
type="button"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faTrashAlt}
|
||||
title="remove"
|
||||
alt="remove"
|
||||
/>
|
||||
</button>
|
||||
<button onClick={ () => removeApiToken() } className={ styles['remove'] } type="button"><FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faTrashAlt } title="remove" alt="remove" /></button>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
{
|
||||
showingRemoveApiTokenAlert &&
|
||||
<div className={ styles['api-token-control-alert-wrapper'] }>
|
||||
<Alert title={ __( 'Whoa, whoa, whoa!', 'font-awesome' ) } type='warning'>
|
||||
{ __( 'You can\'t remove your API token when "Use a Kit" is active. Switch to "Use CDN" first.', 'font-awesome' ) }
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{showingRemoveApiTokenAlert && (
|
||||
<div className={styles['api-token-control-alert-wrapper']}>
|
||||
<Alert
|
||||
title={__('Whoa, whoa, whoa!', 'font-awesome')}
|
||||
type="warning"
|
||||
>
|
||||
{__('You can\'t remove your API token when "Use a Kit" is active. Switch to "Use CDN" first.', 'font-awesome')}
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const STATUS = {
|
||||
|
@ -257,140 +290,178 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
|||
}
|
||||
|
||||
function KitSelector() {
|
||||
const status =
|
||||
apiToken
|
||||
? kitsQueryStatus.isSubmitting
|
||||
? STATUS.querying
|
||||
: kitsQueryStatus.hasSubmitted
|
||||
? kitsQueryStatus.success
|
||||
? size(kits) > 0
|
||||
? STATUS.kitSelection
|
||||
: STATUS.noKitsFoundAfterQuery
|
||||
: STATUS.networkError
|
||||
: kitTokenActive
|
||||
? STATUS.showingOnlyActiveKit
|
||||
: STATUS.apiTokenReadyNoKitsYet
|
||||
: STATUS.noApiToken
|
||||
const status = apiToken
|
||||
? kitsQueryStatus.isSubmitting
|
||||
? STATUS.querying
|
||||
: kitsQueryStatus.hasSubmitted
|
||||
? kitsQueryStatus.success
|
||||
? size(kits) > 0
|
||||
? STATUS.kitSelection
|
||||
: STATUS.noKitsFoundAfterQuery
|
||||
: STATUS.networkError
|
||||
: kitTokenActive
|
||||
? STATUS.showingOnlyActiveKit
|
||||
: STATUS.apiTokenReadyNoKitsYet
|
||||
: STATUS.noApiToken
|
||||
|
||||
const kitRefreshButton = <button onClick={ () => dispatch(queryKits()) } className={ styles['refresh'] }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faRedo } title="refresh" alt="refresh" />
|
||||
<span>
|
||||
{
|
||||
0 === size(kits)
|
||||
? __( 'Get latest kits data', 'font-awesome' )
|
||||
: __( 'Refresh kits data', 'font-awesome' )
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
const kitRefreshButton = (
|
||||
<button
|
||||
onClick={() => dispatch(queryKits())}
|
||||
className={styles['refresh']}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faRedo}
|
||||
title="refresh"
|
||||
alt="refresh"
|
||||
/>
|
||||
<span>{0 === size(kits) ? __('Get latest kits data', 'font-awesome') : __('Refresh kits data', 'font-awesome')}</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
const activeKitNotice = kitTokenActive
|
||||
? <div className={ styles['wrap-active-kit'] }><p className={ classnames(styles['active-kit'], styles['set']) }><FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faCheckCircle } size="lg" />
|
||||
{
|
||||
sprintf(
|
||||
__( '%s Kit is Currently Active' ),
|
||||
kitTokenActive
|
||||
)
|
||||
}
|
||||
</p></div>
|
||||
: null
|
||||
const activeKitNotice = kitTokenActive ? (
|
||||
<div className={styles['wrap-active-kit']}>
|
||||
<p className={classnames(styles['active-kit'], styles['set'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faCheckCircle}
|
||||
size="lg"
|
||||
/>
|
||||
{sprintf(__('%s Kit is Currently Active'), kitTokenActive)}
|
||||
</p>
|
||||
</div>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div className={styles['kit-selector-container']}>
|
||||
{activeKitNotice}
|
||||
|
||||
return <div className={ styles['kit-selector-container'] }>
|
||||
|
||||
{ activeKitNotice }
|
||||
|
||||
<div className={ styles['wrap-selectkit'] }>
|
||||
<h3 className={ styles['title-selectkit'] }><FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faQuestionCircle } size="lg" />
|
||||
{ __( 'Pick a Kit to Use or Check Settings', 'font-awesome' ) }
|
||||
<div className={styles['wrap-selectkit']}>
|
||||
<h3 className={styles['title-selectkit']}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faQuestionCircle}
|
||||
size="lg"
|
||||
/>
|
||||
{__('Pick a Kit to Use or Check Settings', 'font-awesome')}
|
||||
</h3>
|
||||
<div className={ styles['selectkit'] }>
|
||||
<div className={styles['selectkit']}>
|
||||
<p>
|
||||
{
|
||||
__( 'Refresh your kits data to get the latest kit settings, then select the kit you would like to use. Remember to save when you\'re ready to use it.', 'font-awesome' )
|
||||
}
|
||||
{__(
|
||||
"Refresh your kits data to get the latest kit settings, then select the kit you would like to use. Remember to save when you're ready to use it.",
|
||||
'font-awesome'
|
||||
)}
|
||||
</p>
|
||||
{
|
||||
{
|
||||
noApiToken: 'noApiToken',
|
||||
apiTokenReadyNoKitsYet: <>{ activeKitNotice } { kitRefreshButton }</>,
|
||||
querying:
|
||||
<div>
|
||||
<span>
|
||||
{ __( 'Loading your kits...', 'font-awesome' ) }
|
||||
</span>
|
||||
<span className={ classnames(sharedStyles['submit-status'], sharedStyles['submitting']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={faSpinner} spin/>
|
||||
</span>
|
||||
</div>,
|
||||
|
||||
networkError:
|
||||
<div className={ classnames(sharedStyles['submit-status'], sharedStyles['fail']) }>
|
||||
<div className={ classnames(sharedStyles['fail-icon-container']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSkull } />
|
||||
{
|
||||
noApiToken: 'noApiToken',
|
||||
apiTokenReadyNoKitsYet: (
|
||||
<>
|
||||
{activeKitNotice} {kitRefreshButton}
|
||||
</>
|
||||
),
|
||||
querying: (
|
||||
<div>
|
||||
<span>{__('Loading your kits...', 'font-awesome')}</span>
|
||||
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faSpinner}
|
||||
spin
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className={ sharedStyles['explanation'] }>
|
||||
{ kitsQueryStatus.message }
|
||||
),
|
||||
|
||||
networkError: (
|
||||
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
||||
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faSkull}
|
||||
/>
|
||||
</div>
|
||||
<div className={sharedStyles['explanation']}>{kitsQueryStatus.message}</div>
|
||||
</div>
|
||||
</div>,
|
||||
),
|
||||
|
||||
noKitsFoundAfterQuery:
|
||||
<>
|
||||
<Alert title="Zoinks! Looks like you don't have any kits set up yet." type="info">
|
||||
<p>
|
||||
{ __( 'Head over to Font Awesome to create one, then come back here and refresh your kits.', 'font-awesome' ) } <a rel="noopener noreferrer" target="_blank" href="https://fontawesome.com/kits">
|
||||
{ __( 'Create a kit on Font Awesome', 'font-awesome' ) } <FontAwesomeIcon icon={faExternalLinkAlt} /></a>
|
||||
</p>
|
||||
</Alert>
|
||||
{ kitRefreshButton }
|
||||
</>,
|
||||
noKitsFoundAfterQuery: (
|
||||
<>
|
||||
<Alert
|
||||
title="Zoinks! Looks like you don't have any kits set up yet."
|
||||
type="info"
|
||||
>
|
||||
<p>
|
||||
{__('Head over to Font Awesome to create one, then come back here and refresh your kits.', 'font-awesome')}{' '}
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
href="https://fontawesome.com/kits"
|
||||
>
|
||||
{__('Create a kit on Font Awesome', 'font-awesome')} <FontAwesomeIcon icon={faExternalLinkAlt} />
|
||||
</a>
|
||||
</p>
|
||||
</Alert>
|
||||
{kitRefreshButton}
|
||||
</>
|
||||
),
|
||||
|
||||
kitSelection:
|
||||
<>
|
||||
<div className={ styles['field-kitselect'] }>
|
||||
<select
|
||||
className={ styles['kit-select'] }
|
||||
id="kits"
|
||||
name="kit"
|
||||
onChange={ e => handleKitChange({ kitToken: e.target.value }) }
|
||||
disabled={! masterSubmitButtonShowing }
|
||||
value={ kitToken || '' }
|
||||
>
|
||||
<option key='empty' value=''>{ __( 'Select a kit', 'font-awesome' ) }</option>
|
||||
{
|
||||
kits.map((kit, index) => {
|
||||
return <option key={ index } value={ kit.token }>
|
||||
{ `${ kit.name } (${ kit.token })` }
|
||||
</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
{ kitRefreshButton }
|
||||
</div>
|
||||
</>,
|
||||
kitSelection: (
|
||||
<>
|
||||
<div className={styles['field-kitselect']}>
|
||||
<select
|
||||
className={styles['kit-select']}
|
||||
id="kits"
|
||||
name="kit"
|
||||
onChange={(e) => handleKitChange({ kitToken: e.target.value })}
|
||||
disabled={!masterSubmitButtonShowing}
|
||||
value={kitToken || ''}
|
||||
>
|
||||
<option
|
||||
key="empty"
|
||||
value=""
|
||||
>
|
||||
{__('Select a kit', 'font-awesome')}
|
||||
</option>
|
||||
{kits.map((kit, index) => {
|
||||
return (
|
||||
<option
|
||||
key={index}
|
||||
value={kit.token}
|
||||
>
|
||||
{`${kit.name} (${kit.token})`}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
{kitRefreshButton}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
|
||||
showingOnlyActiveKit:
|
||||
<>
|
||||
{ kitRefreshButton }
|
||||
</>
|
||||
}[status]
|
||||
}
|
||||
showingOnlyActiveKit: <>{kitRefreshButton}</>
|
||||
}[status]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return <div>
|
||||
<div className={ styles['kit-tab-content'] }>
|
||||
{
|
||||
hasSavedApiToken
|
||||
? <>
|
||||
return (
|
||||
<div>
|
||||
<div className={styles['kit-tab-content']}>
|
||||
{hasSavedApiToken ? (
|
||||
<>
|
||||
<ApiTokenControl />
|
||||
<KitSelector />
|
||||
</>
|
||||
: <ApiTokenInput />
|
||||
}
|
||||
) : (
|
||||
<ApiTokenInput />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
KitSelectView.propTypes = {
|
||||
|
|
|
@ -7,18 +7,21 @@ import { __ } from '@wordpress/i18n'
|
|||
import createInterpolateElement from './createInterpolateElement'
|
||||
|
||||
export default function ManageFontAwesomeVersionsSection() {
|
||||
return <div className={ classnames(sharedStyles['explanation'], styles['font-awesome-versions-section']) }>
|
||||
<h2 className={ sharedStyles['section-title'] }>{ __( 'Versions of Font Awesome Active on Your Site', 'font-awesome' ) }</h2>
|
||||
<p>
|
||||
{
|
||||
createInterpolateElement(
|
||||
__( '<b>Registered plugins and themes</b> have opted to share information about the Font Awesome settings they are expecting, and are therefore easier to fix. For the <b>unregistered plugins and themes</b>, which are more unpredictable, we have provided options for you to block their Font Awesome source from loading and causing issues.', 'font-awesome' ),
|
||||
return (
|
||||
<div className={classnames(sharedStyles['explanation'], styles['font-awesome-versions-section'])}>
|
||||
<h2 className={sharedStyles['section-title']}>{__('Versions of Font Awesome Active on Your Site', 'font-awesome')}</h2>
|
||||
<p>
|
||||
{createInterpolateElement(
|
||||
__(
|
||||
'<b>Registered plugins and themes</b> have opted to share information about the Font Awesome settings they are expecting, and are therefore easier to fix. For the <b>unregistered plugins and themes</b>, which are more unpredictable, we have provided options for you to block their Font Awesome source from loading and causing issues.',
|
||||
'font-awesome'
|
||||
),
|
||||
{
|
||||
b: <b />
|
||||
}
|
||||
)
|
||||
}
|
||||
</p>
|
||||
<ClientPreferencesView />
|
||||
</div>
|
||||
)}
|
||||
</p>
|
||||
<ClientPreferencesView />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,44 +6,34 @@ import KitConfigView from './KitConfigView'
|
|||
import sharedStyles from './App.module.css'
|
||||
import optionStyles from './CdnConfigView.module.css'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faDotCircle,
|
||||
faSpinner,
|
||||
faCheck,
|
||||
faSkull,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faDotCircle, faSpinner, faCheck, faSkull } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faCircle } from '@fortawesome/free-regular-svg-icons'
|
||||
import classnames from 'classnames'
|
||||
import styles from './SettingsTab.module.css'
|
||||
import has from 'lodash/has'
|
||||
import { has, size } from 'lodash'
|
||||
import { addPendingOption, submitPendingOptions, chooseAwayFromKitConfig, chooseIntoKitConfig } from './store/actions'
|
||||
import CheckingOptionStatusIndicator from './CheckingOptionsStatusIndicator'
|
||||
import size from 'lodash/size'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
|
||||
export default function SettingsTab() {
|
||||
const dispatch = useDispatch()
|
||||
const alreadyUsingKit = useSelector( state => !!state.options.kitToken )
|
||||
const alreadyUsingKit = useSelector((state) => !!state.options.kitToken)
|
||||
const [useKit, setUseKit] = useState(alreadyUsingKit)
|
||||
const isChecking = useSelector(state => state.preferenceConflictDetection.isChecking)
|
||||
const hasSubmitted = useSelector(state => state.optionsFormState.hasSubmitted)
|
||||
const submitSuccess = useSelector(state => state.optionsFormState.success)
|
||||
const submitMessage = useSelector(state => state.optionsFormState.message)
|
||||
const isSubmitting = useSelector(state => state.optionsFormState.isSubmitting)
|
||||
const pendingOptions = useSelector(state => state.pendingOptions)
|
||||
const apiToken = useSelector(state => state.options.apiToken)
|
||||
const [ masterSubmitButtonShowing, setMasterSubmitButtonShowing ] = useState( true )
|
||||
const isChecking = useSelector((state) => state.preferenceConflictDetection.isChecking)
|
||||
const hasSubmitted = useSelector((state) => state.optionsFormState.hasSubmitted)
|
||||
const submitSuccess = useSelector((state) => state.optionsFormState.success)
|
||||
const submitMessage = useSelector((state) => state.optionsFormState.message)
|
||||
const isSubmitting = useSelector((state) => state.optionsFormState.isSubmitting)
|
||||
const pendingOptions = useSelector((state) => state.pendingOptions)
|
||||
const apiToken = useSelector((state) => state.options.apiToken)
|
||||
const [masterSubmitButtonShowing, setMasterSubmitButtonShowing] = useState(true)
|
||||
|
||||
function useOption(option) {
|
||||
return useSelector(state =>
|
||||
has(state.pendingOptions, option)
|
||||
? state.pendingOptions[option]
|
||||
: state.options[option]
|
||||
)
|
||||
return useSelector((state) => (has(state.pendingOptions, option) ? state.pendingOptions[option] : state.options[option]))
|
||||
}
|
||||
|
||||
function handleSubmit(e) {
|
||||
if(!!e && 'function' == typeof e.preventDefault) {
|
||||
if (!!e && 'function' == typeof e.preventDefault) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
|
@ -51,10 +41,10 @@ export default function SettingsTab() {
|
|||
}
|
||||
|
||||
// The kitToken that may be a pendingOption
|
||||
const kitToken = useOption( 'kitToken' )
|
||||
const kitToken = useOption('kitToken')
|
||||
|
||||
// The one that's actually saved in the database already
|
||||
const activeKitToken = useSelector( state => state.options.kitToken )
|
||||
const activeKitToken = useSelector((state) => state.options.kitToken)
|
||||
|
||||
function handleOptionChange(change = {}) {
|
||||
dispatch(addPendingOption(change))
|
||||
|
@ -66,132 +56,153 @@ export default function SettingsTab() {
|
|||
* that a kit selection might have put onto the form.
|
||||
*/
|
||||
function handleSwitchAwayFromKitConfig() {
|
||||
setUseKit( false )
|
||||
setUseKit(false)
|
||||
|
||||
dispatch( chooseAwayFromKitConfig({ activeKitToken }) )
|
||||
dispatch(chooseAwayFromKitConfig({ activeKitToken }))
|
||||
}
|
||||
|
||||
function handleSwitchToKitConfig() {
|
||||
setUseKit( true )
|
||||
setMasterSubmitButtonShowing( true )
|
||||
setUseKit(true)
|
||||
setMasterSubmitButtonShowing(true)
|
||||
|
||||
dispatch( chooseIntoKitConfig() )
|
||||
dispatch(chooseIntoKitConfig())
|
||||
}
|
||||
|
||||
return <div><div className={ sharedStyles['wrapper-div'] }>
|
||||
<h3>{ __( 'How are you using Font Awesome?', 'font-awesome' ) }</h3>
|
||||
<div className={ styles['select-config-container'] }>
|
||||
<span>
|
||||
<input
|
||||
id="select_use_kits"
|
||||
name="select_use_kits"
|
||||
type="radio"
|
||||
value={ useKit }
|
||||
checked={ useKit }
|
||||
onChange={ () => handleSwitchToKitConfig() }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom']) }
|
||||
/>
|
||||
<label htmlFor="select_use_kits" className={ optionStyles['option-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<FontAwesomeIcon
|
||||
icon={ faDotCircle }
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
return (
|
||||
<div>
|
||||
<div className={sharedStyles['wrapper-div']}>
|
||||
<h3>{__('How are you using Font Awesome?', 'font-awesome')}</h3>
|
||||
<div className={styles['select-config-container']}>
|
||||
<span>
|
||||
<input
|
||||
id="select_use_kits"
|
||||
name="select_use_kits"
|
||||
type="radio"
|
||||
value={useKit}
|
||||
checked={useKit}
|
||||
onChange={() => handleSwitchToKitConfig()}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCircle }
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
<span className={ optionStyles['option-label-text'] }>
|
||||
{ __( 'Use A Kit', 'font-awesome' ) }
|
||||
</span>
|
||||
</label>
|
||||
</span>
|
||||
<span>
|
||||
<input
|
||||
id="select_use_cdn"
|
||||
name="select_use_cdn"
|
||||
type="radio"
|
||||
value={ ! useKit }
|
||||
checked={ ! useKit }
|
||||
onChange={ () => handleSwitchAwayFromKitConfig() }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom']) }
|
||||
/>
|
||||
<label htmlFor="select_use_cdn" className={ optionStyles['option-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<FontAwesomeIcon
|
||||
icon={ faDotCircle }
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCircle }
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
<span className={ optionStyles['option-label-text'] }>
|
||||
{ __( 'Use CDN', 'font-awesome' ) }
|
||||
</span>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<>
|
||||
{
|
||||
useKit
|
||||
? <>
|
||||
<KitSelectView useOption={ useOption } handleOptionChange={ handleOptionChange } handleSubmit={ handleSubmit } masterSubmitButtonShowing={ masterSubmitButtonShowing } setMasterSubmitButtonShowing={ setMasterSubmitButtonShowing }/>
|
||||
{ !!kitToken && <KitConfigView kitToken={ kitToken } /> }
|
||||
</>
|
||||
: <CdnConfigView useOption={ useOption } handleOptionChange={ handleOptionChange } handleSubmit={ handleSubmit }/>
|
||||
}
|
||||
</>
|
||||
</div>
|
||||
{
|
||||
(!useKit || ( apiToken && masterSubmitButtonShowing ) ) &&
|
||||
<div className={ classnames(sharedStyles['submit-wrapper'], ['submit']) }>
|
||||
<input
|
||||
type="submit"
|
||||
name="submit"
|
||||
id="submit"
|
||||
className="button button-primary"
|
||||
value={ __( 'Save Changes', 'font-awesome' ) }
|
||||
disabled={ size(pendingOptions) === 0 }
|
||||
onClick={ handleSubmit }
|
||||
/>
|
||||
{ hasSubmitted
|
||||
? submitSuccess
|
||||
? <span className={ classnames(sharedStyles['submit-status'], sharedStyles['success']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faCheck } />
|
||||
<label
|
||||
htmlFor="select_use_kits"
|
||||
className={optionStyles['option-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={faDotCircle}
|
||||
className={sharedStyles['checked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCircle}
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
: <div className={ classnames(sharedStyles['submit-status'], sharedStyles['fail']) }>
|
||||
<div className={ classnames(sharedStyles['fail-icon-container']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSkull } />
|
||||
</div>
|
||||
<div className={ sharedStyles['explanation'] }>
|
||||
{ submitMessage }
|
||||
</div>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
{
|
||||
isSubmitting
|
||||
? <span className={ classnames(sharedStyles['submit-status'], sharedStyles['submitting']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={faSpinner} spin/>
|
||||
</span>
|
||||
: isChecking
|
||||
? <CheckingOptionStatusIndicator/>
|
||||
: size(pendingOptions) > 0
|
||||
? <span className={ sharedStyles['submit-status'] }>{ __( 'you have pending changes', 'font-awesome' ) }</span>
|
||||
: null
|
||||
}
|
||||
<span className={optionStyles['option-label-text']}>{__('Use A Kit', 'font-awesome')}</span>
|
||||
</label>
|
||||
</span>
|
||||
<span>
|
||||
<input
|
||||
id="select_use_cdn"
|
||||
name="select_use_cdn"
|
||||
type="radio"
|
||||
value={!useKit}
|
||||
checked={!useKit}
|
||||
onChange={() => handleSwitchAwayFromKitConfig()}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||
/>
|
||||
<label
|
||||
htmlFor="select_use_cdn"
|
||||
className={optionStyles['option-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={faDotCircle}
|
||||
className={sharedStyles['checked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCircle}
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
<span className={optionStyles['option-label-text']}>{__('Use CDN', 'font-awesome')}</span>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<>
|
||||
{useKit ? (
|
||||
<>
|
||||
<KitSelectView
|
||||
useOption={useOption}
|
||||
handleOptionChange={handleOptionChange}
|
||||
handleSubmit={handleSubmit}
|
||||
masterSubmitButtonShowing={masterSubmitButtonShowing}
|
||||
setMasterSubmitButtonShowing={setMasterSubmitButtonShowing}
|
||||
/>
|
||||
{!!kitToken && <KitConfigView kitToken={kitToken} />}
|
||||
</>
|
||||
) : (
|
||||
<CdnConfigView
|
||||
useOption={useOption}
|
||||
handleOptionChange={handleOptionChange}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{(!useKit || (apiToken && masterSubmitButtonShowing)) && (
|
||||
<div className={classnames(sharedStyles['submit-wrapper'], ['submit'])}>
|
||||
<input
|
||||
type="submit"
|
||||
name="submit"
|
||||
id="submit"
|
||||
className="button button-primary"
|
||||
value={__('Save Changes', 'font-awesome')}
|
||||
disabled={size(pendingOptions) === 0}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
{hasSubmitted ? (
|
||||
submitSuccess ? (
|
||||
<span className={classnames(sharedStyles['submit-status'], sharedStyles['success'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faCheck}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
||||
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faSkull}
|
||||
/>
|
||||
</div>
|
||||
<div className={sharedStyles['explanation']}>{submitMessage}</div>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
{isSubmitting ? (
|
||||
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faSpinner}
|
||||
spin
|
||||
/>
|
||||
</span>
|
||||
) : isChecking ? (
|
||||
<CheckingOptionStatusIndicator />
|
||||
) : size(pendingOptions) > 0 ? (
|
||||
<span className={sharedStyles['submit-status']}>{__('you have pending changes', 'font-awesome')}</span>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react'
|
||||
import ManageFontAwesomeVersionsSection from './ManageFontAwesomeVersionsSection'
|
||||
import UnregisteredClientsView from './UnregisteredClientsView'
|
||||
import V3DeprecationWarning from './V3DeprecationWarning'
|
||||
import ConflictDetectionScannerSection from './ConflictDetectionScannerSection'
|
||||
import sharedStyles from './App.module.css'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
@ -12,24 +11,20 @@ import {
|
|||
resetPendingBlocklistSubmissionStatus,
|
||||
resetUnregisteredClientsDeletionStatus
|
||||
} from './store/actions'
|
||||
import {
|
||||
faCheck,
|
||||
faSkull,
|
||||
faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faCheck, faSkull, faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||
import classnames from 'classnames'
|
||||
import size from 'lodash/size'
|
||||
import { size } from 'lodash'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
|
||||
export default function TroubleshootTab() {
|
||||
const dispatch = useDispatch()
|
||||
const hasV3DeprecationWarning = useSelector(state => !!state.v3DeprecationWarning)
|
||||
const unregisteredClients = useSelector(state => state.unregisteredClients)
|
||||
const unregisteredClients = useSelector((state) => state.unregisteredClients)
|
||||
|
||||
const blocklistUpdateStatus = useSelector(state => state.blocklistUpdateStatus)
|
||||
const unregisteredClientsDeletionStatus = useSelector(state => state.unregisteredClientsDeletionStatus)
|
||||
const blocklistUpdateStatus = useSelector((state) => state.blocklistUpdateStatus)
|
||||
const unregisteredClientsDeletionStatus = useSelector((state) => state.unregisteredClientsDeletionStatus)
|
||||
|
||||
const showSubmitButton = size( unregisteredClients ) > 0
|
||||
const hasPendingChanges = null !== blocklistUpdateStatus.pending || size( unregisteredClientsDeletionStatus.pending ) > 0
|
||||
const showSubmitButton = size(unregisteredClients) > 0
|
||||
const hasPendingChanges = null !== blocklistUpdateStatus.pending || size(unregisteredClientsDeletionStatus.pending) > 0
|
||||
const hasSubmitted = unregisteredClientsDeletionStatus.hasSubmitted || blocklistUpdateStatus.hasSubmitted
|
||||
const isSubmitting = unregisteredClientsDeletionStatus.isSubmitting || blocklistUpdateStatus.isSubmitting
|
||||
|
||||
|
@ -41,68 +36,73 @@ export default function TroubleshootTab() {
|
|||
function handleSubmitClick(e) {
|
||||
e.preventDefault()
|
||||
|
||||
if ( blocklistUpdateStatus.pending ) {
|
||||
if (blocklistUpdateStatus.pending) {
|
||||
dispatch(submitPendingBlocklist())
|
||||
} else {
|
||||
dispatch(resetPendingBlocklistSubmissionStatus())
|
||||
}
|
||||
|
||||
if ( size( unregisteredClientsDeletionStatus.pending ) > 0 ) {
|
||||
if (size(unregisteredClientsDeletionStatus.pending) > 0) {
|
||||
dispatch(submitPendingUnregisteredClientDeletions())
|
||||
} else {
|
||||
dispatch(resetUnregisteredClientsDeletionStatus())
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className={ sharedStyles['wrapper-div'] }>
|
||||
{ hasV3DeprecationWarning && <V3DeprecationWarning /> }
|
||||
<ConflictDetectionScannerSection />
|
||||
<ManageFontAwesomeVersionsSection />
|
||||
<UnregisteredClientsView />
|
||||
</div>
|
||||
{
|
||||
showSubmitButton &&
|
||||
<div className={ classnames(sharedStyles['submit-wrapper'], ['submit']) }>
|
||||
return (
|
||||
<>
|
||||
<div className={sharedStyles['wrapper-div']}>
|
||||
<ConflictDetectionScannerSection />
|
||||
<ManageFontAwesomeVersionsSection />
|
||||
<UnregisteredClientsView />
|
||||
</div>
|
||||
{showSubmitButton && (
|
||||
<div className={classnames(sharedStyles['submit-wrapper'], ['submit'])}>
|
||||
<input
|
||||
type="submit"
|
||||
name="submit"
|
||||
id="submit"
|
||||
className="button button-primary"
|
||||
value={ __( 'Save Changes', 'font-awesome' ) }
|
||||
disabled={ !hasPendingChanges }
|
||||
onClick={ handleSubmitClick }
|
||||
value={__('Save Changes', 'font-awesome')}
|
||||
disabled={!hasPendingChanges}
|
||||
onClick={handleSubmitClick}
|
||||
/>
|
||||
{ hasSubmitted
|
||||
? submitSuccess
|
||||
? <span className={ classnames(sharedStyles['submit-status'], sharedStyles['success']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faCheck } />
|
||||
</span>
|
||||
: <div className={ classnames(sharedStyles['submit-status'], sharedStyles['fail']) }>
|
||||
<div className={ classnames(sharedStyles['fail-icon-container']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSkull } />
|
||||
</div>
|
||||
<div className={ sharedStyles['explanation'] }>
|
||||
{
|
||||
!!blocklistUpdateStatus.message && <p> { blocklistUpdateStatus.message } </p>
|
||||
}
|
||||
{
|
||||
!!unregisteredClientsDeletionStatus.message && <p> { unregisteredClientsDeletionStatus.message } </p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
{
|
||||
isSubmitting
|
||||
? <span className={ classnames(sharedStyles['submit-status'], sharedStyles['submitting']) }>
|
||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={faSpinner} spin/>
|
||||
{hasSubmitted ? (
|
||||
submitSuccess ? (
|
||||
<span className={classnames(sharedStyles['submit-status'], sharedStyles['success'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faCheck}
|
||||
/>
|
||||
</span>
|
||||
: hasPendingChanges
|
||||
? <span className={ sharedStyles['submit-status'] }>{ __( 'you have pending changes', 'font-awesome' ) }</span>
|
||||
: null
|
||||
}
|
||||
) : (
|
||||
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
||||
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faSkull}
|
||||
/>
|
||||
</div>
|
||||
<div className={sharedStyles['explanation']}>
|
||||
{!!blocklistUpdateStatus.message && <p> {blocklistUpdateStatus.message} </p>}
|
||||
{!!unregisteredClientsDeletionStatus.message && <p> {unregisteredClientsDeletionStatus.message} </p>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
{isSubmitting ? (
|
||||
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
||||
<FontAwesomeIcon
|
||||
className={sharedStyles['icon']}
|
||||
icon={faSpinner}
|
||||
spin
|
||||
/>
|
||||
</span>
|
||||
) : hasPendingChanges ? (
|
||||
<span className={sharedStyles['submit-status']}>{__('you have pending changes', 'font-awesome')}</span>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
import React from 'react'
|
||||
import { mount } from 'enzyme'
|
||||
import { Provider } from 'react-redux'
|
||||
import { createStore } from './store'
|
||||
import ErrorBoundary from './ErrorBoundary'
|
||||
import TroubleshootTab from './TroubleshootTab'
|
||||
import { unmountComponentAtNode } from 'react-dom'
|
||||
|
||||
describe('TroubleshootTab', () => {
|
||||
let wrapper = null
|
||||
|
||||
beforeEach(() => {
|
||||
const initialData = JSON.parse(
|
||||
'{"apiNonce":"81245cfaf6","apiUrl":"http://localhost:8765/index.php?rest_route=/font-awesome/v1","detectConflictsUntil":"1581103894","unregisteredClients":{"3c937b6d9b50371df1e78b5d70e11512":{"type":"fontawesome-conflict","technology":"webfont","href":"https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.css","innerText":"","tagName":"LINK","blocked":true},"c604f60e1488e3c3493a19a43709b4ca":{"type":"fontawesome-conflict","technology":"webfont","href":"http://localhost:8765/wp-content/plugins/plugin-kappa/font-awesome-4.7.0/css/font-awesome.css","innerText":"","tagName":"LINK","blocked":true}},"showConflictDetectionReporter":"","settingsPageUrl":"http://localhost:8765/wp-admin/options-general.php?page=font-awesome","activeAdminTab":"ADMIN_TAB_TROUBLESHOOT","options":{"usePro":false,"compat":true,"technology":"webfont","pseudoElements":false,"version":"5.12.1"},"showAdmin":"1","onSettingsPage":"1","clientPreferences":{"beta-plugin":{"name":"beta-plugin","compat":true,"version":[["5.1.0",">="]]}},"releases":{"available":["5.12.1","5.12.0","5.11.2","5.11.1","5.11.0","5.10.2","5.10.1","5.10.0","5.9.0","5.8.2","5.8.1","5.8.0","5.7.2","5.7.1","5.7.0","5.6.3","5.6.1","5.6.0","5.5.0","5.4.2","5.4.1","5.3.1","5.2.0","5.1.1","5.1.0","5.0.13","5.0.12","5.0.10","5.0.9","5.0.8","5.0.6","5.0.4","5.0.3","5.0.2","5.0.1"],"latest_version_5":"5.12.1","latest_version_6":"6.1.1"},"pluginVersion":"4.0.0-rc13","preferenceConflicts":[],"v3DeprecationWarning":""}'
|
||||
)
|
||||
|
||||
global['__FontAwesomeOfficialPlugin__'] = initialData
|
||||
|
||||
const store = createStore(initialData)
|
||||
|
||||
wrapper = mount(
|
||||
<ErrorBoundary>
|
||||
<Provider store={ store }>
|
||||
<TroubleshootTab/>
|
||||
</Provider>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.unmount()
|
||||
wrapper = null
|
||||
})
|
||||
|
||||
test('mounts successfully', () => {
|
||||
expect(wrapper).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('when starting with all conflicts blocked', () => {
|
||||
beforeEach(() => {
|
||||
// Assert expectations of the state of the data
|
||||
const inputs = [
|
||||
'block_all_detected_conflicts',
|
||||
'block_3c937b6d9b50371df1e78b5d70e11512',
|
||||
'block_c604f60e1488e3c3493a19a43709b4ca'
|
||||
]
|
||||
|
||||
inputs.forEach(id => {
|
||||
expect(
|
||||
wrapper
|
||||
.find(`#${id}`)
|
||||
.first()
|
||||
.props()
|
||||
.checked
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
expect(
|
||||
wrapper.find('#submit').props().disabled
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
describe('when de-selecting and re-selecting an individual conflict for blocking', () => {
|
||||
test('there are no pending changes shown', () => {
|
||||
// change/click one of the conflicts
|
||||
wrapper.find('#block_c604f60e1488e3c3493a19a43709b4ca').simulate('change')
|
||||
|
||||
// That one should now be unchecked
|
||||
expect(
|
||||
wrapper
|
||||
.find('#block_c604f60e1488e3c3493a19a43709b4ca')
|
||||
.first()
|
||||
.props()
|
||||
.checked
|
||||
).toBe(false)
|
||||
|
||||
// The All select should no longer be checked
|
||||
expect(
|
||||
wrapper
|
||||
.find('#block_all_detected_conflicts')
|
||||
.first()
|
||||
.props()
|
||||
.checked
|
||||
).toBe(false)
|
||||
|
||||
// And there should be pending changes
|
||||
expect(
|
||||
wrapper.find('#submit').props().disabled
|
||||
).toBe(false)
|
||||
|
||||
// Now, just click/change that same one again
|
||||
wrapper.find('#block_c604f60e1488e3c3493a19a43709b4ca').simulate('change')
|
||||
|
||||
// That one should now be checked
|
||||
expect(
|
||||
wrapper
|
||||
.find('#block_c604f60e1488e3c3493a19a43709b4ca')
|
||||
.first()
|
||||
.props()
|
||||
.checked
|
||||
).toBe(true)
|
||||
|
||||
// The All select should be checked again
|
||||
expect(
|
||||
wrapper
|
||||
.find('#block_all_detected_conflicts')
|
||||
.first()
|
||||
.props()
|
||||
.checked
|
||||
).toBe(true)
|
||||
|
||||
// And the submit button should again be disabled, for lack of pending changes
|
||||
expect(
|
||||
wrapper.find('#submit').props().disabled
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,31 +1,20 @@
|
|||
import React from 'react'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import {
|
||||
updatePendingBlocklist,
|
||||
updatePendingUnregisteredClientsForDeletion
|
||||
} from './store/actions'
|
||||
import { updatePendingBlocklist, updatePendingUnregisteredClientsForDeletion } from './store/actions'
|
||||
import { blocklistSelector } from './store/reducers'
|
||||
import styles from './UnregisteredClientsView.module.css'
|
||||
import sharedStyles from './App.module.css'
|
||||
import classnames from 'classnames'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faCheckSquare,
|
||||
faThumbsUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faSquare } from '@fortawesome/free-regular-svg-icons'
|
||||
import get from 'lodash/get'
|
||||
import truncate from 'lodash/truncate'
|
||||
import size from 'lodash/size'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
import sortedUnique from 'lodash/sortedUniq'
|
||||
import difference from 'lodash/difference'
|
||||
import { faCheckSquare, faThumbsUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faSquare } from '@fortawesome/free-regular-svg-icons'
|
||||
import { get, truncate, size, isEqual, sortedUniq, difference } from 'lodash'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import createInterpolateElement from './createInterpolateElement'
|
||||
|
||||
function excerpt( content ) {
|
||||
if( !! content ) {
|
||||
return truncate( content, { length: 100 } )
|
||||
function excerpt(content) {
|
||||
if (!!content) {
|
||||
return truncate(content, { length: 100 })
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
@ -33,232 +22,229 @@ function excerpt( content ) {
|
|||
|
||||
export default function UnregisteredClientsView() {
|
||||
const dispatch = useDispatch()
|
||||
const unregisteredClients = useSelector(state => state.unregisteredClients)
|
||||
const savedBlocklist = useSelector(state => blocklistSelector(state))
|
||||
const blocklist = useSelector(state => {
|
||||
if( null !== state.blocklistUpdateStatus.pending ) {
|
||||
const unregisteredClients = useSelector((state) => state.unregisteredClients)
|
||||
const savedBlocklist = useSelector((state) => blocklistSelector(state))
|
||||
const blocklist = useSelector((state) => {
|
||||
if (null !== state.blocklistUpdateStatus.pending) {
|
||||
return state.blocklistUpdateStatus.pending
|
||||
} else {
|
||||
return savedBlocklist
|
||||
}
|
||||
})
|
||||
const deleteList = useSelector( state => state.unregisteredClientsDeletionStatus.pending)
|
||||
const deleteList = useSelector((state) => state.unregisteredClientsDeletionStatus.pending)
|
||||
const detectedUnregisteredClients = size(Object.keys(unregisteredClients)) > 0
|
||||
const allDetectedConflictsSelectedForBlocking =
|
||||
isEqual(Object.keys(unregisteredClients).sort(), [...(blocklist || [])].sort())
|
||||
const allDetectedConflictsSelectedForRemoval =
|
||||
isEqual(Object.keys(unregisteredClients).sort(), [...(deleteList || [])].sort())
|
||||
const allDetectedConflictsSelectedForBlocking = isEqual(Object.keys(unregisteredClients).sort(), [...(blocklist || [])].sort())
|
||||
const allDetectedConflictsSelectedForRemoval = isEqual(Object.keys(unregisteredClients).sort(), [...(deleteList || [])].sort())
|
||||
const allDetectedConflicts = Object.keys(unregisteredClients)
|
||||
|
||||
function isCheckedForBlocking(md5) {
|
||||
return !! blocklist.find(x => x === md5)
|
||||
return !!blocklist.find((x) => x === md5)
|
||||
}
|
||||
|
||||
function isCheckedForRemoval(md5) {
|
||||
return !! deleteList.find(x => x === md5)
|
||||
return !!deleteList.find((x) => x === md5)
|
||||
}
|
||||
|
||||
function changeCheckForRemoval(md5, allDetectedConflicts) {
|
||||
const newDeleteList = 'all' === md5
|
||||
? allDetectedConflictsSelectedForRemoval
|
||||
? [] // uncheck them all
|
||||
: allDetectedConflicts // check them all
|
||||
: isCheckedForRemoval(md5)
|
||||
? deleteList.filter(x => x !== md5)
|
||||
const newDeleteList =
|
||||
'all' === md5
|
||||
? allDetectedConflictsSelectedForRemoval
|
||||
? [] // uncheck them all
|
||||
: allDetectedConflicts // check them all
|
||||
: isCheckedForRemoval(md5)
|
||||
? deleteList.filter((x) => x !== md5)
|
||||
: [...deleteList, md5]
|
||||
|
||||
|
||||
dispatch(updatePendingUnregisteredClientsForDeletion(newDeleteList))
|
||||
}
|
||||
|
||||
function changeCheckForBlocking(md5, allDetectedConflicts) {
|
||||
const newBlocklist = 'all' === md5
|
||||
? allDetectedConflictsSelectedForBlocking
|
||||
? [] // uncheck them all
|
||||
: allDetectedConflicts // check them all
|
||||
: isCheckedForBlocking(md5)
|
||||
? blocklist.filter(x => x !== md5)
|
||||
const newBlocklist =
|
||||
'all' === md5
|
||||
? allDetectedConflictsSelectedForBlocking
|
||||
? [] // uncheck them all
|
||||
: allDetectedConflicts // check them all
|
||||
: isCheckedForBlocking(md5)
|
||||
? blocklist.filter((x) => x !== md5)
|
||||
: [...blocklist, md5]
|
||||
|
||||
const orig = sortedUnique( savedBlocklist )
|
||||
const updated = sortedUnique( newBlocklist )
|
||||
const orig = sortedUniq(savedBlocklist)
|
||||
const updated = sortedUniq(newBlocklist)
|
||||
|
||||
if(
|
||||
orig.length === updated.length &&
|
||||
0 === size( difference(orig, updated) ) &&
|
||||
0 === size( difference(updated, orig) )
|
||||
) {
|
||||
if (orig.length === updated.length && 0 === size(difference(orig, updated)) && 0 === size(difference(updated, orig))) {
|
||||
dispatch(updatePendingBlocklist(null))
|
||||
} else {
|
||||
dispatch(updatePendingBlocklist(newBlocklist))
|
||||
}
|
||||
}
|
||||
|
||||
return <div className={ classnames(styles['unregistered-clients'], { [styles['none-detected']]: !detectedUnregisteredClients }) }>
|
||||
<h3 className={ sharedStyles['section-title'] }>{ __( 'Other themes or plugins', 'font-awesome' ) }</h3>
|
||||
{detectedUnregisteredClients
|
||||
? <div>
|
||||
return (
|
||||
<div className={classnames(styles['unregistered-clients'], { [styles['none-detected']]: !detectedUnregisteredClients })}>
|
||||
<h3 className={sharedStyles['section-title']}>{__('Other themes or plugins', 'font-awesome')}</h3>
|
||||
{detectedUnregisteredClients ? (
|
||||
<div>
|
||||
<p className={sharedStyles['explanation']}>
|
||||
{
|
||||
__( 'Below is the list of other versions of Font Awesome from active plugins or themes that are loading on your site. Check off any that you would like to block from loading. Normally this just blocks the conflicting version of Font Awesome and doesn\'t affect the other functions of the plugin, but you should verify your site works as expected. If you think you\'ve fixed a found conflict, you can clear it from the table.', 'font-awesome' )
|
||||
}
|
||||
{__(
|
||||
"Below is the list of other versions of Font Awesome from active plugins or themes that are loading on your site. Check off any that you would like to block from loading. Normally this just blocks the conflicting version of Font Awesome and doesn't affect the other functions of the plugin, but you should verify your site works as expected. If you think you've fixed a found conflict, you can clear it from the table.",
|
||||
'font-awesome'
|
||||
)}
|
||||
</p>
|
||||
<table className={classnames('widefat', 'striped')}>
|
||||
<thead>
|
||||
<tr className={sharedStyles['table-header']}>
|
||||
<th>
|
||||
<div className={ styles['column-label'] }>{ __( 'Block', 'font-awesome' ) }</div>
|
||||
{
|
||||
size( allDetectedConflicts ) > 1 &&
|
||||
<div className={ styles['block-all-container'] }>
|
||||
<input
|
||||
id='block_all_detected_conflicts'
|
||||
name='block_all_detected_conflicts'
|
||||
type="checkbox"
|
||||
value='all'
|
||||
checked={ allDetectedConflictsSelectedForBlocking }
|
||||
onChange={ () => changeCheckForBlocking('all', allDetectedConflicts) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom']) }
|
||||
/>
|
||||
<label htmlFor='block_all_detected_conflicts' className={ styles['checkbox-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCheckSquare }
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faSquare }
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
{ __( 'All', 'font-awesome' ) }
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</th>
|
||||
<th>
|
||||
<span className={ styles['column-label'] }>
|
||||
{ __( 'Type', 'font-awesome' ) }
|
||||
</span>
|
||||
</th>
|
||||
<th>
|
||||
<span className={ styles['column-label'] }>
|
||||
{ __( 'URL', 'font-awesome' ) }
|
||||
</span>
|
||||
</th>
|
||||
<th>
|
||||
<div className={ styles['column-label'] }>{ __( 'Clear', 'font-awesome' ) }</div>
|
||||
{
|
||||
size( allDetectedConflicts ) > 1 &&
|
||||
<div className={ styles['remove-all-container'] }>
|
||||
<input
|
||||
id='remove_all_detected_conflicts'
|
||||
name='remove_all_detected_conflicts'
|
||||
type="checkbox"
|
||||
value='all'
|
||||
checked={ allDetectedConflictsSelectedForRemoval }
|
||||
onChange={ () => changeCheckForRemoval('all', allDetectedConflicts) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom']) }
|
||||
/>
|
||||
<label htmlFor='remove_all_detected_conflicts' className={ styles['checkbox-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCheckSquare }
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faSquare }
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
{ __( 'All', 'font-awesome' ) }
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</th>
|
||||
</tr>
|
||||
<tr className={sharedStyles['table-header']}>
|
||||
<th>
|
||||
<div className={styles['column-label']}>{__('Block', 'font-awesome')}</div>
|
||||
{size(allDetectedConflicts) > 1 && (
|
||||
<div className={styles['block-all-container']}>
|
||||
<input
|
||||
id="block_all_detected_conflicts"
|
||||
name="block_all_detected_conflicts"
|
||||
type="checkbox"
|
||||
value="all"
|
||||
checked={allDetectedConflictsSelectedForBlocking}
|
||||
onChange={() => changeCheckForBlocking('all', allDetectedConflicts)}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
||||
/>
|
||||
<label
|
||||
htmlFor="block_all_detected_conflicts"
|
||||
className={styles['checkbox-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckSquare}
|
||||
className={sharedStyles['checked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faSquare}
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
{__('All', 'font-awesome')}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</th>
|
||||
<th>
|
||||
<span className={styles['column-label']}>{__('Type', 'font-awesome')}</span>
|
||||
</th>
|
||||
<th>
|
||||
<span className={styles['column-label']}>{__('URL', 'font-awesome')}</span>
|
||||
</th>
|
||||
<th>
|
||||
<div className={styles['column-label']}>{__('Clear', 'font-awesome')}</div>
|
||||
{size(allDetectedConflicts) > 1 && (
|
||||
<div className={styles['remove-all-container']}>
|
||||
<input
|
||||
id="remove_all_detected_conflicts"
|
||||
name="remove_all_detected_conflicts"
|
||||
type="checkbox"
|
||||
value="all"
|
||||
checked={allDetectedConflictsSelectedForRemoval}
|
||||
onChange={() => changeCheckForRemoval('all', allDetectedConflicts)}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
||||
/>
|
||||
<label
|
||||
htmlFor="remove_all_detected_conflicts"
|
||||
className={styles['checkbox-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckSquare}
|
||||
className={sharedStyles['checked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faSquare}
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
{__('All', 'font-awesome')}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
allDetectedConflicts.map(md5 => (
|
||||
{allDetectedConflicts.map((md5) => (
|
||||
<tr key={md5}>
|
||||
<td>
|
||||
<input
|
||||
id={`block_${md5}`}
|
||||
name={`block_${md5}`}
|
||||
type="checkbox"
|
||||
value={ md5 }
|
||||
checked={ isCheckedForBlocking(md5) }
|
||||
onChange={ () => changeCheckForBlocking(md5) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom']) }
|
||||
value={md5}
|
||||
checked={isCheckedForBlocking(md5)}
|
||||
onChange={() => changeCheckForBlocking(md5)}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
||||
/>
|
||||
<label htmlFor={`block_${md5}`} className={ styles['checkbox-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<label
|
||||
htmlFor={`block_${md5}`}
|
||||
className={styles['checkbox-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCheckSquare }
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
icon={faCheckSquare}
|
||||
className={sharedStyles['checked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faSquare }
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
icon={faSquare}
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</td>
|
||||
<td>{get(unregisteredClients[md5], 'tagName', 'unknown').toLowerCase()}</td>
|
||||
<td>
|
||||
{get(unregisteredClients[md5], 'tagName', 'unknown').toLowerCase()}
|
||||
</td>
|
||||
<td>
|
||||
{
|
||||
unregisteredClients[md5].src
|
||||
|| unregisteredClients[md5].href
|
||||
|| createInterpolateElement(
|
||||
__( '<em>in page source. </em><excerpt/>', 'font-awesome' ),
|
||||
{
|
||||
em: <em />,
|
||||
excerpt: (
|
||||
( content ) => content
|
||||
? <>
|
||||
File starts with: <code>{ content }</code>
|
||||
</>
|
||||
: ''
|
||||
) ( excerpt( get(unregisteredClients[md5], 'innerText') ) )
|
||||
}
|
||||
)
|
||||
}
|
||||
{unregisteredClients[md5].src ||
|
||||
unregisteredClients[md5].href ||
|
||||
createInterpolateElement(__('<em>in page source. </em><excerpt/>', 'font-awesome'), {
|
||||
em: <em />,
|
||||
excerpt: ((content) =>
|
||||
content ? (
|
||||
<>
|
||||
File starts with: <code>{content}</code>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
))(excerpt(get(unregisteredClients[md5], 'innerText')))
|
||||
})}
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
id={`remove_${md5}`}
|
||||
name={`remove_${md5}`}
|
||||
type="checkbox"
|
||||
value={ md5 }
|
||||
checked={ isCheckedForRemoval(md5) }
|
||||
onChange={ () => changeCheckForRemoval(md5) }
|
||||
className={ classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom']) }
|
||||
value={md5}
|
||||
checked={isCheckedForRemoval(md5)}
|
||||
onChange={() => changeCheckForRemoval(md5)}
|
||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
||||
/>
|
||||
<label htmlFor={`remove_${md5}`} className={ styles['checkbox-label'] }>
|
||||
<span className={ sharedStyles['relative'] }>
|
||||
<label
|
||||
htmlFor={`remove_${md5}`}
|
||||
className={styles['checkbox-label']}
|
||||
>
|
||||
<span className={sharedStyles['relative']}>
|
||||
<FontAwesomeIcon
|
||||
icon={ faCheckSquare }
|
||||
className={ sharedStyles['checked-icon'] }
|
||||
icon={faCheckSquare}
|
||||
className={sharedStyles['checked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={ faSquare }
|
||||
className={ sharedStyles['unchecked-icon'] }
|
||||
icon={faSquare}
|
||||
className={sharedStyles['unchecked-icon']}
|
||||
size="lg"
|
||||
fixedWidth
|
||||
/>
|
||||
|
@ -266,19 +252,21 @@ export default function UnregisteredClientsView() {
|
|||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
: <div className={ classnames(sharedStyles['explanation'], sharedStyles['flex'], sharedStyles['flex-row'] )}>
|
||||
) : (
|
||||
<div className={classnames(sharedStyles['explanation'], sharedStyles['flex'], sharedStyles['flex-row'])}>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={ faThumbsUp } size='lg'/>
|
||||
<FontAwesomeIcon
|
||||
icon={faThumbsUp}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div className={ sharedStyles['space-left'] }>
|
||||
{ __( 'We haven\'t detected any plugins or themes trying to load Font Awesome.', 'font-awesome' ) }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className={sharedStyles['space-left']}>{__("We haven't detected any plugins or themes trying to load Font Awesome.", 'font-awesome')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
import React from 'react'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faClock, faSpinner, faCheck, faSkull } from '@fortawesome/free-solid-svg-icons'
|
||||
import { snoozeV3DeprecationWarning } from './store/actions'
|
||||
import styles from './V3DeprecationWarning.module.css'
|
||||
import classnames from 'classnames'
|
||||
import Alert from './Alert'
|
||||
import { __, sprintf } from '@wordpress/i18n'
|
||||
import createInterpolateElement from './createInterpolateElement'
|
||||
|
||||
export default function V3DeprecationWarning() {
|
||||
const { snooze, atts, v5name, v5prefix } = useSelector(state => state.v3DeprecationWarning)
|
||||
const { isSubmitting, hasSubmitted, success } = useSelector(state => state.v3DeprecationWarningStatus)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
if (snooze) return null
|
||||
|
||||
return <Alert
|
||||
title={ __( 'Font Awesome 3 icon names are deprecated', 'font-awesome' ) }
|
||||
type='warning'
|
||||
>
|
||||
<p>
|
||||
{
|
||||
createInterpolateElement(
|
||||
sprintf(
|
||||
__('Looks like you\'re using an old Font Awesome 3 icon name in your shortcode: <code>%s</code>. We discontinued support for Font Awesome 3 quite some time ago. Won\'t you jump into <a>the newest Font Awesome</a> with us? It\'s way better, and it\'s easy to upgrade.', 'font-awesome' ),
|
||||
atts.name
|
||||
),
|
||||
{
|
||||
code: <code />,
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
a: <a rel="noopener noreferrer" target="_blank" href="https://fontawesome.com/" />
|
||||
}
|
||||
)
|
||||
}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{ __('Just adjust your shortcode from this:', 'font-awesome' ) }
|
||||
</p>
|
||||
|
||||
<blockquote><code>[icon name="{ atts.name }"]</code></blockquote>
|
||||
|
||||
<p>
|
||||
{ __( 'to this:', 'font-awesome' ) }
|
||||
</p>
|
||||
|
||||
<blockquote><code>[icon name="{ v5name }" prefix="{ v5prefix }"]</code></blockquote>
|
||||
|
||||
<p>
|
||||
{
|
||||
createInterpolateElement(
|
||||
__( 'You\'ll need to go adjust any version 3 icon names in [icon] shortcodes in your pages, posts, widgets, templates (or wherever they\'re coming from) to the new format with prefix. You can check the icon names and prefixes in our <linkIconGallery>Icon Gallery</linkIconGallery>. But what\'s that prefix, you ask? We now support a number of different styles for each icon. <linkLearnMore>Learn more</linkLearnMore>', 'font-awesome' ),
|
||||
{
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
linkIconGallery: <a rel="noopener noreferrer" target="_blank" href="https://fontawesome.com/icons?d=gallery" />,
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
linkLearnMore: <a rel="noopener noreferrer" target="_blank" href="https://fontawesome.com/how-to-use/on-the-web/setup/upgrading-from-version-4#changes" />
|
||||
}
|
||||
)
|
||||
}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{
|
||||
createInterpolateElement(
|
||||
__( 'Once you update your icon shortcodes, this warning will disappear or you could hit snooze to hide it for a while. <strong>But we\'re gonna remove this v3-to-v5 magic soon, though, so don\'t wait forever.</strong>', 'font-awesome' ),
|
||||
{
|
||||
strong: <strong />
|
||||
}
|
||||
)
|
||||
}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<button disabled={ isSubmitting } onClick={ () => dispatch(snoozeV3DeprecationWarning()) } className={ classnames( styles['snooze-button'], 'button', 'button-primary' ) }>
|
||||
{
|
||||
isSubmitting
|
||||
? <FontAwesomeIcon icon={ faSpinner } spin className={ styles['submitting'] } />
|
||||
: hasSubmitted
|
||||
? success
|
||||
? <FontAwesomeIcon icon={ faCheck } className={ styles['success'] }/>
|
||||
: <FontAwesomeIcon icon={ faSkull } className={ styles['fail'] }/>
|
||||
: <FontAwesomeIcon icon={ faClock } className={ styles['snooze'] }/>
|
||||
}
|
||||
<span className={ styles['label'] }>{ __( 'Snooze', 'font-awesome' ) }</span>
|
||||
</button>
|
||||
</p>
|
||||
</Alert>
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
.v3-deprecation-warning {
|
||||
border: 1px solid black;
|
||||
background-color: #fdfdf3;
|
||||
padding: 1.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.snooze-button {
|
||||
padding: .5rem;
|
||||
background-color: rgba(0,0,0,0);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.snooze-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.snooze-button .label {
|
||||
margin-left: 1em;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import get from 'lodash/get'
|
||||
import set from 'lodash/set'
|
||||
const get = require('lodash/get')
|
||||
const set = require('lodash/set')
|
||||
|
||||
// NOTE: the Jest docs on manual mocks indicate that mocks for things under
|
||||
// node_modules should be in a __mocks__ directory that is adjacent to node_modules.
|
||||
|
@ -9,10 +9,10 @@ import set from 'lodash/set'
|
|||
// the root for Jest in such a way that __mocks__ as to live under the src directory.
|
||||
// See: https://github.com/facebook/create-react-app/issues/7539#issuecomment-531463603
|
||||
|
||||
const DEFAULT_INTERCEPTOR = thing => thing
|
||||
const DEFAULT_PUT = ( url, _data, _config ) => handleRequest( { url, method: 'PUT' } )
|
||||
const DEFAULT_POST = ( url, _data, _config ) => handleRequest( { url, method: 'POST' } )
|
||||
const DEFAULT_DELETE = ( url, _data, _config ) => handleRequest( { url, method: 'DELETE' } )
|
||||
const DEFAULT_INTERCEPTOR = (thing) => thing
|
||||
const DEFAULT_PUT = (url, _data, _config) => handleRequest({ url, method: 'PUT' })
|
||||
const DEFAULT_POST = (url, _data, _config) => handleRequest({ url, method: 'POST' })
|
||||
const DEFAULT_DELETE = (url, _data, _config) => handleRequest({ url, method: 'DELETE' })
|
||||
let responses = {}
|
||||
let responseSuccessInterceptor = DEFAULT_INTERCEPTOR
|
||||
let responseFailureInterceptor = DEFAULT_INTERCEPTOR
|
||||
|
@ -33,13 +33,15 @@ const axios = {
|
|||
|
||||
axios.create = () => axios
|
||||
|
||||
export function respondWith ({ url, method = "GET", response }) {
|
||||
export function respondWith({ url, method = 'GET', response }) {
|
||||
responses = set(responses, [url, method.toUpperCase()], response)
|
||||
}
|
||||
|
||||
export function resetAxiosMocks () {
|
||||
export function resetAxiosMocks() {
|
||||
responses = {}
|
||||
axios.put = DEFAULT_PUT
|
||||
axios.post = DEFAULT_POST
|
||||
axios.delete = DEFAULT_DELETE
|
||||
}
|
||||
|
||||
export function changeImpl({ name, fn }) {
|
||||
|
@ -51,28 +53,28 @@ function handleRequest(req) {
|
|||
|
||||
const response = get(responses, [url, method.toUpperCase()])
|
||||
|
||||
if ( !response ) {
|
||||
if (!response) {
|
||||
console.log('No prepared response for:', req) // eslint-disable-line no-console
|
||||
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
if ( response instanceof XMLHttpRequest ) {
|
||||
return responseFailureInterceptor( { request: response } )
|
||||
if (response instanceof XMLHttpRequest) {
|
||||
return responseFailureInterceptor({ request: response })
|
||||
}
|
||||
|
||||
if ( response instanceof Error ) {
|
||||
return responseFailureInterceptor( { message: response.message } )
|
||||
if (response instanceof Error) {
|
||||
return responseFailureInterceptor({ message: response.message })
|
||||
}
|
||||
|
||||
const status = get( response, 'status' )
|
||||
const status = get(response, 'status')
|
||||
|
||||
// TODO: use axios validateStatus to determine resolve or reject, instead
|
||||
// of hardcoding the default
|
||||
if ( response && status && status < 300 ) {
|
||||
return Promise.resolve( responseSuccessInterceptor( response ) )
|
||||
if (response && status && status < 300) {
|
||||
return Promise.resolve(responseSuccessInterceptor(response))
|
||||
} else {
|
||||
return responseFailureInterceptor( { response } )
|
||||
return responseFailureInterceptor({ response })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
import { Modal } from '@wordpress/components'
|
||||
import { FaIconChooser } from '@fortawesome/fa-icon-chooser-react'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import createInterpolateElement from '../createInterpolateElement'
|
||||
|
||||
const IconChooserModal = (props) => {
|
||||
const { onSubmit, kitToken, version, pro, handleQuery, modalOpenEvent, getUrlText, settingsPageUrl } = props
|
||||
const [ isOpen, setOpen ] = useState( false )
|
||||
|
||||
document.addEventListener(modalOpenEvent.type, () => setOpen(true))
|
||||
|
||||
const closeModal = () => setOpen( false )
|
||||
|
||||
const submitAndCloseModal = (result) => {
|
||||
if('function' === typeof onSubmit) {
|
||||
onSubmit(result)
|
||||
}
|
||||
closeModal()
|
||||
}
|
||||
|
||||
const isProCdn = !!pro && !kitToken
|
||||
|
||||
return (
|
||||
<>
|
||||
{ isOpen && (
|
||||
<Modal title="Add a Font Awesome Icon" onRequestClose={ closeModal }>
|
||||
{
|
||||
isProCdn &&
|
||||
<div style={{ margin: '1em', backgroundColor: '#FFD200', padding: '1em', borderRadius: '.5em', fontSize: '15px'}}>
|
||||
{__( 'Looking for Pro icons and styles? You’ll need to use a kit. ', 'font-awesome' ) }
|
||||
|
||||
<a href={settingsPageUrl}>{__('Go to Font Awesome Plugin Settings', 'font-awesome')}</a>
|
||||
</div>
|
||||
}
|
||||
<FaIconChooser
|
||||
version={ version }
|
||||
kitToken={ kitToken }
|
||||
handleQuery={ handleQuery }
|
||||
getUrlText={ getUrlText }
|
||||
onFinish={ result => submitAndCloseModal(result) }
|
||||
searchInputPlaceholder={__('Find icons by name, category, or keyword', 'font-awesome')}
|
||||
>
|
||||
<span slot='fatal-error-heading'>
|
||||
{ __('Well, this is awkward...', 'font-awesome') }
|
||||
</span>
|
||||
<span slot='fatal-error-detail'>
|
||||
{ __('Something has gone horribly wrong. Check the console for additional error information.', 'font-awesome') }
|
||||
</span>
|
||||
<span slot="start-view-heading">
|
||||
{__( "Font Awesome is the web's most popular icon set, with tons of icons in a variety of styles.", 'font-awesome' ) }
|
||||
</span>
|
||||
<span slot="start-view-detail">
|
||||
{
|
||||
createInterpolateElement(
|
||||
__( "Not sure where to start? Here are some favorites, or try a search for <strong>spinners</strong>, <strong>shopping</strong>, <strong>food</strong>, or <strong>whatever you're looking for</strong>.", 'font-awesome'),
|
||||
{
|
||||
strong: <strong/>
|
||||
}
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<span slot='search-field-label-free'>
|
||||
{__('Search Font Awesome Free Icons in Version', 'font-awesome')}
|
||||
</span>
|
||||
<span slot='search-field-label-pro'>
|
||||
{__('Search Font Awesome Pro Icons in Version', 'font-awesome')}
|
||||
</span>
|
||||
<span slot='searching-free'>
|
||||
{__("You're searching Font Awesome Free icons in version", 'font-awesome')}
|
||||
</span>
|
||||
<span slot='searching-pro'>
|
||||
{__("You're searching Font Awesome Pro icons in version", 'font-awesome')}
|
||||
</span>
|
||||
<span slot='light-requires-pro'>
|
||||
{__('You need to use a Pro kit to get Light icons.', 'font-awesome')}
|
||||
</span>
|
||||
<span slot='thin-requires-pro'>
|
||||
{__('You need to use a Pro kit with Version 6 to get Thin icons.', 'font-awesome')}
|
||||
</span>
|
||||
<span slot='duotone-requires-pro'>
|
||||
{__('You need to use a Pro kit with Version 5.10 or later to get Duotone icons.', 'font-awesome')}
|
||||
</span>
|
||||
<span slot='uploaded-requires-pro'>
|
||||
{__('You need to use a Pro kit to get Uploaded icons.', 'font-awesome')}
|
||||
</span>
|
||||
<span slot='kit-has-no-uploaded-icons'>
|
||||
{__('This kit contains no uploaded icons.', 'font-awesome')}
|
||||
</span>
|
||||
<span slot='no-search-results-heading'>
|
||||
{__("Sorry, we couldn't find anything for that.", 'font-awesome')}
|
||||
</span>
|
||||
<span slot='no-search-results-detail'>
|
||||
{__('You might try a different search...', 'font-awesome')}
|
||||
</span>
|
||||
<span slot="suggest-icon-upload">
|
||||
{
|
||||
createInterpolateElement(
|
||||
__( 'Or <a>upload your own icon</a> to a Pro kit!', 'font-awesome'),
|
||||
{
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
a: <a target="_blank" rel="noopener noreferrer" href="https://fontawesome.com/v5.15/how-to-use/on-the-web/using-kits/uploading-icons" />
|
||||
}
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<span slot='get-fontawesome-pro'>
|
||||
{
|
||||
createInterpolateElement(
|
||||
__( 'Or <a>use Font Awesome Pro</a> for more icons and styles!', 'font-awesome'),
|
||||
{
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
a: <a target="_blank" rel="noopener noreferrer" href="https://fontawesome.com/" />
|
||||
}
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<span slot='initial-loading-view-heading'>
|
||||
{__('Fetching icons', 'font-awesome') }
|
||||
</span>
|
||||
<span slot='initial-loading-view-detail'>
|
||||
{__('When this thing gets up to 88 mph...', 'font-awesome')}
|
||||
</span>
|
||||
</FaIconChooser>
|
||||
</Modal>
|
||||
) }
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconChooserModal
|
|
@ -1,87 +0,0 @@
|
|||
import IconChooserModal from './IconChooserModal'
|
||||
import { buildShortCodeFromIconChooserResult } from './shortcode'
|
||||
import { Fragment, Component } from '@wordpress/element'
|
||||
import { SVG, Path } from '@wordpress/components'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { insert, registerFormatType } from '@wordpress/rich-text'
|
||||
import { RichTextToolbarButton } from '@wordpress/block-editor'
|
||||
|
||||
export function setupBlockEditor (params) {
|
||||
// TODO: is this the right block type name for what we're doing here?
|
||||
const name = 'font-awesome/icon'
|
||||
const title = __('Font Awesome Icon')
|
||||
|
||||
const {
|
||||
modalOpenEvent,
|
||||
kitToken,
|
||||
version,
|
||||
pro,
|
||||
handleQuery,
|
||||
getUrlText,
|
||||
settingsPageUrl
|
||||
} = params
|
||||
|
||||
registerFormatType(name, {
|
||||
name,
|
||||
title: __( 'Font Awesome Icon' ),
|
||||
keywords: [ __( 'icon' ), __( 'font awesome' ) ],
|
||||
tagName: 'i',
|
||||
className: null,
|
||||
// if object is true, then the HTML rendered for this type will lack a closing tag.
|
||||
object: false,
|
||||
edit: class FontAwesomeIconEdit extends Component {
|
||||
constructor(props) {
|
||||
super( ...arguments )
|
||||
|
||||
this.handleFormatButtonClick = this.handleFormatButtonClick.bind(this)
|
||||
this.handleSelect = this.handleSelect.bind(this)
|
||||
}
|
||||
|
||||
handleFormatButtonClick() {
|
||||
document.dispatchEvent(modalOpenEvent)
|
||||
}
|
||||
|
||||
handleSelect(event){
|
||||
const { value, onChange } = this.props
|
||||
// TODO: this would indicate an invalid event. Do we want some error handling here?
|
||||
if(!event.detail) return
|
||||
|
||||
const shortcode = buildShortCodeFromIconChooserResult(event.detail)
|
||||
onChange( insert( value, shortcode ) )
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<RichTextToolbarButton
|
||||
icon={
|
||||
<SVG
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512"
|
||||
className="svg-inline--fa fa-font-awesome fa-w-14">
|
||||
<Path
|
||||
fill="currentColor"
|
||||
d="M397.8 32H50.2C22.7 32 0 54.7 0 82.2v347.6C0 457.3 22.7 480 50.2 480h347.6c27.5 0 50.2-22.7 50.2-50.2V82.2c0-27.5-22.7-50.2-50.2-50.2zm-45.4 284.3c0 4.2-3.6 6-7.8 7.8-16.7 7.2-34.6 13.7-53.8 13.7-26.9 0-39.4-16.7-71.7-16.7-23.3 0-47.8 8.4-67.5 17.3-1.2.6-2.4.6-3.6 1.2V385c0 1.8 0 3.6-.6 4.8v1.2c-2.4 8.4-10.2 14.3-19.1 14.3-11.3 0-20.3-9-20.3-20.3V166.4c-7.8-6-13.1-15.5-13.1-26.3 0-18.5 14.9-33.5 33.5-33.5 18.5 0 33.5 14.9 33.5 33.5 0 10.8-4.8 20.3-13.1 26.3v18.5c1.8-.6 3.6-1.2 5.4-2.4 18.5-7.8 40.6-14.3 61.5-14.3 22.7 0 40.6 6 60.9 13.7 4.2 1.8 8.4 2.4 13.1 2.4 22.7 0 47.8-16.1 53.8-16.1 4.8 0 9 3.6 9 7.8v140.3z"
|
||||
/>
|
||||
</SVG>
|
||||
}
|
||||
title={ title }
|
||||
onClick={ this.handleFormatButtonClick }
|
||||
/>
|
||||
<IconChooserModal
|
||||
modalOpenEvent={ modalOpenEvent }
|
||||
kitToken={ kitToken }
|
||||
version={ version }
|
||||
pro={ pro }
|
||||
settingsPageUrl={ settingsPageUrl }
|
||||
handleQuery={ handleQuery }
|
||||
onSubmit={ this.handleSelect }
|
||||
getUrlText={ getUrlText }
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import IconChooserModal from './IconChooserModal'
|
||||
import { buildShortCodeFromIconChooserResult } from './shortcode'
|
||||
import get from 'lodash/get'
|
||||
|
||||
export function handleSubmit(event) {
|
||||
const insert = get(window, 'wp.media.editor.insert')
|
||||
insert && insert( buildShortCodeFromIconChooserResult(event.detail) )
|
||||
}
|
||||
|
||||
export function setupClassicEditor(params) {
|
||||
const {
|
||||
iconChooserContainerId,
|
||||
modalOpenEvent,
|
||||
kitToken,
|
||||
version,
|
||||
pro,
|
||||
handleQuery,
|
||||
getUrlText,
|
||||
settingsPageUrl
|
||||
} = params
|
||||
|
||||
const container = document.querySelector(`#${iconChooserContainerId}`)
|
||||
if(!container) return
|
||||
if(!window.tinymce) return
|
||||
|
||||
let wpComponentsStyleAdded = false
|
||||
|
||||
if(!wpComponentsStyleAdded) {
|
||||
wpComponentsStyleAdded = true
|
||||
|
||||
import('@wordpress/components/build-style/style.css')
|
||||
.then(() => {})
|
||||
.catch(err =>
|
||||
console.error(
|
||||
'Font Awesome Plugin failed to load styles for the Icon Chooser in the Classic Editor',
|
||||
err
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: consider how to add Font Awesome to the Tiny MCE visual pane.
|
||||
// But there maybe unexpected behaviors.
|
||||
/*
|
||||
const editor = tinymce.activeEditor
|
||||
|
||||
editor.on('init', e => {
|
||||
const script = editor.dom.doc.createElement('script')
|
||||
script.setAttribute('src', 'https://kit.fontawesome.com/fakekit.js')
|
||||
script.setAttribute('crossorigin', 'anonymous')
|
||||
editor.dom.doc.head.appendChild(script)
|
||||
})
|
||||
*/
|
||||
|
||||
ReactDOM.render(
|
||||
<IconChooserModal
|
||||
kitToken={ kitToken }
|
||||
version={ version }
|
||||
pro={ pro }
|
||||
modalOpenEvent={ modalOpenEvent }
|
||||
handleQuery={ handleQuery }
|
||||
settingsPageUrl={ settingsPageUrl }
|
||||
onSubmit={ handleSubmit }
|
||||
getUrlText={ getUrlText }
|
||||
/>,
|
||||
container
|
||||
)
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import apiFetch from '@wordpress/api-fetch'
|
||||
|
||||
const configureQueryHandler = params => async (query) => {
|
||||
try {
|
||||
const { apiNonce, rootUrl, restApiNamespace } = params
|
||||
|
||||
// If apiFetch is from wp.apiFetch, it may already have RootURLMiddleware set up.
|
||||
// If we're using the fallback (i.e. when running in the Classic Editor), then
|
||||
// it doesn't yet have thr RootURLMiddleware.
|
||||
// We want to guarantee that it's there, so we'll always add it.
|
||||
// So what if it was already there? Experiment seems to have shown that this
|
||||
// is idempotent. It doesn't seem to hurt to just do it again, so we will.
|
||||
apiFetch.use( apiFetch.createRootURLMiddleware( rootUrl ) )
|
||||
|
||||
// We need the nonce to be set up because we're going to run our query through
|
||||
// the API controller end point, which requires non-public authorization.
|
||||
apiFetch.use( apiFetch.createNonceMiddleware( apiNonce ) )
|
||||
|
||||
return await apiFetch( {
|
||||
path: `${restApiNamespace}/api`,
|
||||
method: 'POST',
|
||||
body: query
|
||||
} )
|
||||
} catch( error ) {
|
||||
console.error('CAUGHT:', error)
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export default configureQueryHandler
|
|
@ -1,51 +0,0 @@
|
|||
import get from 'lodash/get'
|
||||
import { setupBlockEditor } from './blockEditor'
|
||||
import { setupClassicEditor } from './classicEditor'
|
||||
|
||||
let classicEditorSetupComplete = false
|
||||
|
||||
function setupClassicEditorIconChooser(initialParams) {
|
||||
// We only want to do this once.
|
||||
if(classicEditorSetupComplete) return
|
||||
if(!window.tinymce) return
|
||||
|
||||
const params = {
|
||||
...initialParams,
|
||||
iconChooserContainerId: 'font-awesome-icon-chooser-container',
|
||||
iconChooserMediaButtonClass: 'font-awesome-icon-chooser-media-button'
|
||||
}
|
||||
|
||||
setupClassicEditor(params)
|
||||
|
||||
classicEditorSetupComplete = true
|
||||
}
|
||||
|
||||
export function setupIconChooser(initialParams) {
|
||||
const params = {
|
||||
...initialParams,
|
||||
modalOpenEvent: new Event('fontAwesomeIconChooserOpen', { "bubbles": true, "cancelable": false })
|
||||
}
|
||||
|
||||
window['__FontAwesomeOfficialPlugin__openIconChooserModal'] = () => {
|
||||
document.dispatchEvent(params.modalOpenEvent)
|
||||
}
|
||||
|
||||
if( !! get(initialParams, 'isGutenbergPage') ) {
|
||||
setupBlockEditor(params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tiny MCE loading time: In WordPress 5, it's straightforward to enqueue
|
||||
* this script with a script dependency of wp-tinymce. But that's not available
|
||||
* in WP 4, and there doesn't seem to be any way to ensure that the Tiny MCE
|
||||
* script has been loaded before this, other than to add a script after the
|
||||
* Tiny MCE scripts have been printed.
|
||||
*
|
||||
* So what we'll do instead is simply export this function that can be exposed
|
||||
* as a global function, and in our back end PHP code, we'll add an inline script
|
||||
* to invoke that global for tinyMCE setup if and when it is necessary.
|
||||
*/
|
||||
return {
|
||||
setupClassicEditorIconChooser: () => setupClassicEditorIconChooser(params)
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export const GLOBAL_KEY = '__FontAwesomeOfficialPlugin__'
|
|
@ -1,8 +1,11 @@
|
|||
import { createStore } from './store'
|
||||
import get from 'lodash/get'
|
||||
import { get, set } from 'lodash'
|
||||
import { GLOBAL_KEY } from './constants'
|
||||
import createInterpolateElement from './createInterpolateElement'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
|
||||
const initialData = window['__FontAwesomeOfficialPlugin__']
|
||||
__webpack_public_path__ = get(initialData, 'webpackPublicPath')
|
||||
const initialData = window[GLOBAL_KEY]
|
||||
// See: https://webpack.js.org/guides/public-path/#on-the-fly
|
||||
const CONFLICT_DETECTION_REPORT_EVENT_TYPE = 'fontAwesomeConflictDetectionReport'
|
||||
/**
|
||||
* This will start out as falsy, when there's a report, we'll set it with those
|
||||
|
@ -14,8 +17,8 @@ const CONFLICT_DETECTION_REPORT_EVENT_TYPE = 'fontAwesomeConflictDetectionReport
|
|||
*/
|
||||
let conflictDetectionReport = null
|
||||
|
||||
if( get(initialData, 'showConflictDetectionReporter') ) {
|
||||
const reportEvent = new Event(CONFLICT_DETECTION_REPORT_EVENT_TYPE, { "bubbles": true, "cancelable": false })
|
||||
if (get(initialData, 'showConflictDetectionReporter')) {
|
||||
const reportEvent = new Event(CONFLICT_DETECTION_REPORT_EVENT_TYPE, { bubbles: true, cancelable: false })
|
||||
|
||||
/**
|
||||
* If we're doing conflict detection, we must set this up before DOMContentLoaded,
|
||||
|
@ -23,127 +26,51 @@ if( get(initialData, 'showConflictDetectionReporter') ) {
|
|||
*/
|
||||
window.FontAwesomeDetection = {
|
||||
...(window.FontAwesomeDetection || {}),
|
||||
report: params => {
|
||||
report: (params) => {
|
||||
conflictDetectionReport = params
|
||||
document.dispatchEvent(reportEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* First, we need to resolve whether we're using external dependencies available
|
||||
* in WordPress 5 core, or whether we've loaded our WordPress 4 compatibility
|
||||
* bundle. Regardless, the webpack config will use this global for settting up
|
||||
* externals.
|
||||
*/
|
||||
if( !window.__Font_Awesome_Webpack_Externals__ ) {
|
||||
window.__Font_Awesome_Webpack_Externals__ = {
|
||||
React: get(window, 'React'),
|
||||
ReactDOM: get(window, 'ReactDOM'),
|
||||
i18n: get(window, 'wp.i18n'),
|
||||
apiFetch: get(window, 'wp.apiFetch'),
|
||||
components: get(window, 'wp.components'),
|
||||
element: get(window, 'wp.element'),
|
||||
richText: get(window, 'wp.richText'),
|
||||
blockEditor: get(window, 'wp.blockEditor'),
|
||||
domReady: get(window, 'wp.domReady')
|
||||
}
|
||||
}
|
||||
|
||||
const { __ } = __Font_Awesome_Webpack_Externals__.i18n
|
||||
|
||||
if(! initialData){
|
||||
console.error( __( 'Font Awesome plugin is broken: initial state data missing.', 'font-awesome' ) )
|
||||
if (!initialData) {
|
||||
console.error(__('Font Awesome plugin is broken: initial state data missing.', 'font-awesome'))
|
||||
}
|
||||
|
||||
const store = createStore(initialData)
|
||||
|
||||
const {
|
||||
showAdmin,
|
||||
showConflictDetectionReporter,
|
||||
enableIconChooser,
|
||||
usingCompatJs,
|
||||
isGutenbergPage
|
||||
} = store.getState()
|
||||
set(window, [GLOBAL_KEY, 'createInterpolateElement'], createInterpolateElement)
|
||||
|
||||
if( showAdmin ) {
|
||||
const { showAdmin, showConflictDetectionReporter } = store.getState()
|
||||
|
||||
if (showAdmin) {
|
||||
import('./mountAdminView')
|
||||
.then(({ default: mountAdminView }) => {
|
||||
mountAdminView(store)
|
||||
})
|
||||
.catch(error => {
|
||||
console.error( __( 'Font Awesome plugin error when initializing admin settings view', 'font-awesome' ), error )
|
||||
})
|
||||
.then(({ default: mountAdminView }) => {
|
||||
mountAdminView(store)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(__('Font Awesome plugin error when initializing admin settings view', 'font-awesome'), error)
|
||||
})
|
||||
}
|
||||
|
||||
if( showConflictDetectionReporter ) {
|
||||
Promise.all([
|
||||
import('./store/actions'),
|
||||
import('./mountConflictDetectionReporter')
|
||||
])
|
||||
.then(([{ reportDetectedConflicts }, { mountConflictDetectionReporter }]) => {
|
||||
const report = params => store.dispatch(reportDetectedConflicts(params))
|
||||
|
||||
/**
|
||||
* If the conflict detection report is already available, just use it;
|
||||
* otherwise, listen for the reporting event.
|
||||
*/
|
||||
if( conflictDetectionReport ) {
|
||||
report(conflictDetectionReport)
|
||||
} else {
|
||||
document.addEventListener(
|
||||
CONFLICT_DETECTION_REPORT_EVENT_TYPE,
|
||||
_event => report(conflictDetectionReport)
|
||||
)
|
||||
}
|
||||
|
||||
mountConflictDetectionReporter(store)
|
||||
})
|
||||
.catch(error => {
|
||||
console.error( __( 'Font Awesome plugin error when initializing conflict detection scanner', 'font-awesome' ), error )
|
||||
})
|
||||
}
|
||||
|
||||
if ( enableIconChooser ) {
|
||||
if ( usingCompatJs && isGutenbergPage ) {
|
||||
console.warn( __( 'Font Awesome Plugin cannot enable the Icon Chooser on a page that includes the block editor (Gutenberg) because it is not compatible with your WordPress installation. Upgrading to at least WordPress 5.4.6 will probably resolve this.', 'font-awesome' ) )
|
||||
} else {
|
||||
Promise.all([
|
||||
import('./chooser'),
|
||||
import('./chooser/handleQuery'),
|
||||
import('./chooser/getUrlText')
|
||||
])
|
||||
.then(([{ setupIconChooser }, { default: configureQueryHandler }, { default: getUrlText } ]) => {
|
||||
const kitToken = get(initialData, 'options.kitToken')
|
||||
const version = get(initialData, 'options.version')
|
||||
|
||||
const params = {
|
||||
...initialData,
|
||||
kitToken,
|
||||
version,
|
||||
getUrlText,
|
||||
pro: get(initialData, 'options.usePro')
|
||||
}
|
||||
|
||||
const handleQuery = configureQueryHandler(params)
|
||||
|
||||
const { setupClassicEditorIconChooser } = setupIconChooser({ ...params, handleQuery })
|
||||
if (showConflictDetectionReporter) {
|
||||
Promise.all([import('./store/actions'), import('./mountConflictDetectionReporter')])
|
||||
.then(([{ reportDetectedConflicts }, { mountConflictDetectionReporter }]) => {
|
||||
const report = (params) => store.dispatch(reportDetectedConflicts(params))
|
||||
|
||||
/**
|
||||
* Tiny MCE will probably be loaded later, but since this code runs async,
|
||||
* we can't guarantee the timing. So if this runs first, it will set this
|
||||
* global to a function that the post-tiny-mce inline code can invoke.
|
||||
* But if that code runs first, it will set this global to some truthy value,
|
||||
* which tells us to invoke this setup immediately.
|
||||
* If the conflict detection report is already available, just use it;
|
||||
* otherwise, listen for the reporting event.
|
||||
*/
|
||||
if( window['__FontAwesomeOfficialPlugin__setupClassicEditorIconChooser'] ) {
|
||||
setupClassicEditorIconChooser()
|
||||
if (conflictDetectionReport) {
|
||||
report(conflictDetectionReport)
|
||||
} else {
|
||||
window['__FontAwesomeOfficialPlugin__setupClassicEditorIconChooser'] = setupClassicEditorIconChooser
|
||||
document.addEventListener(CONFLICT_DETECTION_REPORT_EVENT_TYPE, (_event) => report(conflictDetectionReport))
|
||||
}
|
||||
|
||||
mountConflictDetectionReporter(store)
|
||||
})
|
||||
.catch(error => {
|
||||
console.error( __( 'Font Awesome plugin error when initializing Icon Chooser', 'font-awesome' ), error )
|
||||
.catch((error) => {
|
||||
console.error(__('Font Awesome plugin error when initializing conflict detection scanner', 'font-awesome'), error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,29 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import ErrorBoundary from './ErrorBoundary'
|
||||
import FontAwesomeAdminView from './FontAwesomeAdminView'
|
||||
import { Provider } from 'react-redux'
|
||||
import domReady from '@wordpress/dom-ready'
|
||||
|
||||
export default function(store) {
|
||||
domReady(() =>
|
||||
ReactDOM.render(
|
||||
const isAtLeastReact18 = React.version.split('.')[0] >= 18
|
||||
|
||||
export default function (store) {
|
||||
const container = document.getElementById('font-awesome-admin')
|
||||
|
||||
domReady(() => {
|
||||
const app = (
|
||||
<ErrorBoundary>
|
||||
<Provider store={ store }>
|
||||
<FontAwesomeAdminView/>
|
||||
<Provider store={store}>
|
||||
<FontAwesomeAdminView />
|
||||
</Provider>
|
||||
</ErrorBoundary>,
|
||||
document.getElementById('font-awesome-admin')
|
||||
</ErrorBoundary>
|
||||
)
|
||||
)
|
||||
|
||||
if (isAtLeastReact18) {
|
||||
createRoot(container).render(app)
|
||||
} else {
|
||||
ReactDOM.render(app, container)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import ConflictDetectionReporter from './ConflictDetectionReporter'
|
||||
import { dom } from '@fortawesome/fontawesome-svg-core'
|
||||
import { Provider } from 'react-redux'
|
||||
|
@ -24,22 +24,22 @@ export function mountConflictDetectionReporter(store) {
|
|||
faStyle.appendChild(cssText)
|
||||
|
||||
const shadowContainer = document.createElement('DIV')
|
||||
const root = createRoot(shadowContainer)
|
||||
|
||||
shadow.appendChild(faStyle)
|
||||
shadow.appendChild(shadowContainer)
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={ store }>
|
||||
root.render(
|
||||
<Provider store={store}>
|
||||
<ConflictDetectionReporter />
|
||||
</Provider>,
|
||||
shadowContainer
|
||||
</Provider>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function isConflictDetectionReporterMounted() {
|
||||
const shadowHost = document.getElementById(CONFLICT_DETECTION_SHADOW_HOST_ID)
|
||||
if(! shadowHost ) return false
|
||||
if (!shadowHost) return false
|
||||
|
||||
return !!shadowHost.shadowRoot
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { test as setup, expect } from '@wordpress/e2e-test-utils-playwright'
|
||||
import '../support/env.js'
|
||||
|
||||
const authFile = 'src/playwright/.auth/state.json'
|
||||
|
||||
setup('authenticate', async ({ page }) => {
|
||||
await page.goto('/wp-login.php')
|
||||
await page.getByLabel('Username or Email Address').fill(process.env.WP_ADMIN_USERNAME)
|
||||
await page.getByLabel('Password', { exact: true }).fill(process.env.WP_ADMIN_PASSWORD)
|
||||
await page.getByRole('button', { name: 'Log In' }).click()
|
||||
await page.waitForURL('**/wp-admin/')
|
||||
await page.context().storageState({ path: authFile })
|
||||
})
|
|
@ -0,0 +1,32 @@
|
|||
import { test as setup, expect } from '@wordpress/e2e-test-utils-playwright'
|
||||
import '../support/env.js'
|
||||
|
||||
const CONFIG_ROUTE_PATTERN = '**/font-awesome/v1/config'
|
||||
const API_ROUTE_PATTERN = '**/font-awesome/v1/api*'
|
||||
|
||||
setup('pro kit', async ({ page }) => {
|
||||
expect(process.env.API_TOKEN).toBeTruthy()
|
||||
expect(process.env.KIT_TOKEN).toBeTruthy()
|
||||
|
||||
await page.goto('/wp-admin/admin.php?page=font-awesome')
|
||||
await page.locator('label').filter({ hasText: 'Use A Kit' }).click()
|
||||
|
||||
const allText = await page.getByRole('heading').allTextContents()
|
||||
|
||||
await page.locator('label').filter({ hasText: 'API Token' }).fill(process.env.API_TOKEN)
|
||||
const saveAPITokenResponsePromise = page.waitForResponse(CONFIG_ROUTE_PATTERN)
|
||||
await page.getByRole('button', { name: 'Save API Token' }).click()
|
||||
await saveAPITokenResponsePromise
|
||||
|
||||
const kitsResponsePromise = page.waitForResponse(API_ROUTE_PATTERN)
|
||||
|
||||
await page.getByRole('button').filter({ hasText: 'kits data' }).click()
|
||||
await kitsResponsePromise
|
||||
|
||||
await page.locator('select').selectOption(process.env.KIT_TOKEN)
|
||||
|
||||
const saveSettingsResponsePromise = page.waitForResponse(CONFIG_ROUTE_PATTERN)
|
||||
|
||||
await page.getByRole('button').filter({ hasText: 'Save Changes' }).click()
|
||||
await saveSettingsResponsePromise
|
||||
})
|
|
@ -0,0 +1,22 @@
|
|||
import { test as setup, expect, RequestUtils } from '@wordpress/e2e-test-utils-playwright'
|
||||
import mysql from 'mysql2/promise'
|
||||
import { prepareRestApi } from '../support/testHelpers'
|
||||
|
||||
setup('reset', async ({ storageState, baseURL }) => {
|
||||
const { requestUtils, requestContext } = await prepareRestApi({ storageState, baseURL })
|
||||
await requestUtils.deactivatePlugin('font-awesome')
|
||||
|
||||
const connection = await mysql.createConnection({
|
||||
host: 'localhost',
|
||||
user: process.env.WORDPRESS_DB_USER,
|
||||
password: process.env.WORDPRESS_DB_PASSWORD,
|
||||
database: process.env.WORDPRESS_DB_NAME
|
||||
})
|
||||
|
||||
const sql = 'DELETE FROM `wp_options` WHERE `option_name` = ? LIMIT 1'
|
||||
await connection.execute(sql, ['font-awesome'])
|
||||
await connection.execute(sql, ['font-awesome-conflict-detection'])
|
||||
await connection.execute(sql, ['font-awesome-releases'])
|
||||
await requestUtils.activatePlugin('font-awesome')
|
||||
await requestContext.dispose()
|
||||
})
|
|
@ -0,0 +1,7 @@
|
|||
import dotenv from 'dotenv'
|
||||
import path from 'path'
|
||||
|
||||
const ROOT_DIR = path.resolve(__dirname, '../../../..')
|
||||
|
||||
dotenv.config({ path: path.resolve(ROOT_DIR, '.env'), override: true })
|
||||
dotenv.config({ path: path.resolve(ROOT_DIR, '.env.local'), override: true })
|
|
@ -0,0 +1,18 @@
|
|||
import { request } from '@playwright/test'
|
||||
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright'
|
||||
|
||||
export async function prepareRestApi({ baseURL, storageState }) {
|
||||
const requestContext = await request.newContext({
|
||||
baseURL
|
||||
})
|
||||
|
||||
const storageStatePath = typeof storageState === 'string' ? storageState : undefined
|
||||
|
||||
const requestUtils = new RequestUtils(requestContext, {
|
||||
storageStatePath
|
||||
})
|
||||
|
||||
await requestUtils.setupRest()
|
||||
|
||||
return { requestUtils, requestContext }
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { expect, test } from '@wordpress/e2e-test-utils-playwright'
|
||||
import { prepareRestApi } from '../support/testHelpers'
|
||||
|
||||
const QUERY = 'query { search(version: "6.x", query: "coffee", first: 1) { id } }'
|
||||
|
||||
// A regression test to ensure that the former way of sending query requests
|
||||
// to the plugin's /api endpoint still works, though it's no longer the recommended
|
||||
// way to do it.
|
||||
//
|
||||
// This test can only be expected to work when mod_security is disabled,
|
||||
// since making a request to the API endpoint with a default (text/plain)
|
||||
// MIME type is known to result in a 403 due to the OWASP default core ruleset
|
||||
// as of OWASP 4.3.0.
|
||||
test('query as plain text', async ({ storageState, baseURL }) => {
|
||||
expect(process.env.ENABLE_MOD_SECURITY).toEqual('false')
|
||||
|
||||
const { requestUtils, requestContext } = await prepareRestApi({ storageState, baseURL })
|
||||
|
||||
const url = `http://${process.env.WP_DOMAIN}/wp-json/font-awesome/v1/api?_locale=user`
|
||||
|
||||
const response = await requestUtils.request.fetch(url, {
|
||||
method: 'POST',
|
||||
data: QUERY,
|
||||
headers: {
|
||||
'X-WP-Nonce': requestUtils.storageState.nonce
|
||||
}
|
||||
})
|
||||
|
||||
expect(response.status()).toEqual(200)
|
||||
|
||||
const responseObj = await response.json()
|
||||
|
||||
expect(responseObj).toHaveProperty('data.search')
|
||||
})
|
|
@ -0,0 +1,15 @@
|
|||
import { expect, test } from '@wordpress/e2e-test-utils-playwright'
|
||||
|
||||
test('change technology', async ({ page }) => {
|
||||
await page.goto('/wp-admin/admin.php?page=font-awesome')
|
||||
|
||||
const preferenceCheckResponsePromise = page.waitForResponse('**/font-awesome/v1/preference-check')
|
||||
|
||||
await page.getByText('SVG').click()
|
||||
|
||||
await preferenceCheckResponsePromise
|
||||
|
||||
const saveChangesResponsePromise = page.waitForResponse('**/font-awesome/v1/config')
|
||||
await page.getByRole('button', { name: 'Save Changes' }).click()
|
||||
await saveChangesResponsePromise
|
||||
})
|
|
@ -0,0 +1,53 @@
|
|||
import { Editor, expect, test } from '@wordpress/e2e-test-utils-playwright'
|
||||
|
||||
test.describe('full site editor', async () => {
|
||||
test.use({
|
||||
editor: async ({ page }, use) => {
|
||||
await use(new Editor({ page }))
|
||||
}
|
||||
})
|
||||
|
||||
test('insert with icon chooser', async ({ page, editor, pageUtils }) => {
|
||||
const pageLoadPromise = page.waitForResponse('**/wp/v2/pages*')
|
||||
|
||||
await page.goto('/wp-admin/site-editor.php?canvas=edit')
|
||||
|
||||
await pageLoadPromise
|
||||
|
||||
const getStartedCount = await page.getByRole('button', { name: 'Get started' }).count()
|
||||
|
||||
if (getStartedCount > 0) {
|
||||
await page.getByRole('button', { name: 'Get started' }).click()
|
||||
}
|
||||
|
||||
await editor.insertBlock({
|
||||
name: 'core/paragraph'
|
||||
})
|
||||
await page.keyboard.type('Here comes an icon: ')
|
||||
|
||||
await editor.clickBlockToolbarButton('More')
|
||||
|
||||
await pageUtils.pressKeys('Enter', 1)
|
||||
|
||||
await page.waitForSelector('fa-icon-chooser input#search')
|
||||
|
||||
const searchResponsePromise = page.waitForResponse('**/font-awesome/v1/api*')
|
||||
|
||||
await page.locator('fa-icon-chooser input#search').fill('coffee')
|
||||
|
||||
await searchResponsePromise
|
||||
|
||||
await page.locator('fa-icon-chooser button.icon').first().click()
|
||||
|
||||
let blocks = null
|
||||
|
||||
try {
|
||||
// On WP 6.0.8, this throws an exception, but it's a false negative.
|
||||
// So, if there's a succesfully call of getBlocks(), we want to
|
||||
// assert its results. But if that fails, don't fail the whole test.
|
||||
blocks = await editor.getBlocks()
|
||||
expect(blocks).toHaveLength(1)
|
||||
expect(blocks[0].attributes.content).toMatch(/\[icon.*?\]$/)
|
||||
} catch (_e) {}
|
||||
})
|
||||
})
|
|
@ -0,0 +1,48 @@
|
|||
import { Editor, test, expect, login, RequestUtils } from '@wordpress/e2e-test-utils-playwright'
|
||||
|
||||
test.describe('blockEditorIconChooser', async () => {
|
||||
test.beforeEach(async ({ admin }) => {
|
||||
await admin.createNewPost()
|
||||
})
|
||||
|
||||
test.use({
|
||||
editor: async ({ page }, use) => {
|
||||
await use(new Editor({ page }))
|
||||
}
|
||||
})
|
||||
|
||||
test('search and select from icon chooser', async ({ editor, page, pageUtils }) => {
|
||||
await editor.insertBlock({
|
||||
name: 'core/paragraph'
|
||||
})
|
||||
await page.keyboard.type('Here comes an icon: ')
|
||||
|
||||
await editor.clickBlockToolbarButton('More')
|
||||
|
||||
await pageUtils.pressKeys('Enter', 1)
|
||||
|
||||
await page.waitForSelector('fa-icon-chooser input#search')
|
||||
|
||||
const searchResponsePromise = page.waitForResponse('**/font-awesome/v1/api*')
|
||||
|
||||
await page.locator('fa-icon-chooser input#search').fill('coffee')
|
||||
|
||||
await searchResponsePromise
|
||||
|
||||
await page.locator('fa-icon-chooser button.icon').first().click()
|
||||
|
||||
let blocks = null
|
||||
|
||||
try {
|
||||
// On WP 6.0.8, this throws an exception, but it's a false negative.
|
||||
// So, if there's a succesfully call of getBlocks(), we want to
|
||||
// assert its results. But if that fails, don't fail the whole test.
|
||||
blocks = await editor.getBlocks()
|
||||
expect(blocks).toHaveLength(1)
|
||||
expect(blocks[0].attributes.content).toMatch(/\[icon.*?\]$/)
|
||||
} catch (_e) {}
|
||||
|
||||
// The loading of the icon chooser should not have messed up the lodash globals.
|
||||
await expect(page.evaluate(() => 'undefined' !== typeof _ && 'undefined' !== typeof lodash)).toBeTruthy()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,41 @@
|
|||
import { expect, test } from '@wordpress/e2e-test-utils-playwright'
|
||||
|
||||
test.describe('conflictScanner', async () => {
|
||||
test.beforeEach(async ({ requestUtils }) => {
|
||||
await requestUtils.activatePlugin('plugin-gamma')
|
||||
})
|
||||
|
||||
test.afterEach(async ({ requestUtils }) => {
|
||||
await requestUtils.deactivatePlugin('plugin-gamma')
|
||||
})
|
||||
|
||||
test('start conflict detection scanner, detect, and block', async ({ page }) => {
|
||||
await page.goto('/wp-admin/admin.php?page=font-awesome')
|
||||
await page.getByRole('button', { name: 'Troubleshoot' }).click()
|
||||
const scannerStartResponsePromise = page.waitForResponse('**/font-awesome/v1/conflict-detection/until')
|
||||
await page.getByRole('button', { name: 'Enable scanner for 10 minutes' }).click()
|
||||
await scannerStartResponsePromise
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Font Awesome Conflict Scanner' })).toBeVisible()
|
||||
|
||||
await expect(page.locator('span').filter({ hasText: 'minutes left to browse' }).first()).toBeVisible()
|
||||
|
||||
const scannerReportResponsePromise = page.waitForResponse('**/font-awesome/v1/conflict-detection/conflicts')
|
||||
await page.goto('/')
|
||||
await expect(page.getByRole('heading', { name: 'Plugin Gamma' })).toBeVisible()
|
||||
|
||||
await scannerReportResponsePromise
|
||||
await expect(page.getByRole('heading', { name: 'Font Awesome Conflict Scanner' })).toBeVisible()
|
||||
|
||||
await expect(page.getByText('Page scan complete')).toBeVisible()
|
||||
await expect(page.getByText('1 new conflicts found on this page')).toBeVisible()
|
||||
await expect(page.getByText('1 total found')).toBeVisible()
|
||||
await page.getByRole('link', { name: 'manage' }).click()
|
||||
await page.getByRole('row', { name: 'link https://cdn.jsdelivr.net' }).locator('svg').nth(1).click()
|
||||
const saveChangesResponsePromise = page.waitForResponse('**/font-awesome/v1/conflict-detection/conflicts/blocklist')
|
||||
await page.getByRole('button', { name: 'Save Changes' }).click()
|
||||
await saveChangesResponsePromise
|
||||
await page.goto('/')
|
||||
await expect(page.getByText('0 new conflicts found on this page')).toBeVisible()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,72 @@
|
|||
const CACHE_KEY_PREFIX = 'wp-font-awesome-cache'
|
||||
|
||||
function buildPrefixedKey(key) {
|
||||
return `${CACHE_KEY_PREFIX}-${key}`
|
||||
}
|
||||
|
||||
// This removes all items in localStorage whose keys begin
|
||||
// with this modules CACHE_KEY_PREFIX.
|
||||
export function clearQueryCache() {
|
||||
if (!window?.localStorage) return
|
||||
|
||||
if (localStorage.length === 0) return
|
||||
|
||||
for (let i = localStorage.length - 1; i >= 0; i--) {
|
||||
const key = localStorage.key(i)
|
||||
if (key.startsWith(CACHE_KEY_PREFIX)) {
|
||||
localStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function remove(prefixedCacheKey) {
|
||||
if ('function' !== typeof window?.localStorage?.removeItem) return
|
||||
localStorage.removeItem(prefixedCacheKey)
|
||||
}
|
||||
|
||||
// Takes a cache that is *not* prefixed.
|
||||
// Expects to be able to add its prefix, and get an item from localStorage
|
||||
// which must be JSON-parseable.
|
||||
//
|
||||
// On success, returns the result of the cache value after JSON.parse().
|
||||
// On failure, returns undefined: if there's no matching key, or an error
|
||||
// in JSON parsing.
|
||||
export function get(key) {
|
||||
if ('function' !== typeof window?.localStorage?.getItem) return
|
||||
|
||||
const prefixedCacheKey = buildPrefixedKey(key)
|
||||
|
||||
const cacheValueJson = localStorage.getItem(prefixedCacheKey)
|
||||
|
||||
try {
|
||||
const cacheValue = JSON.parse(cacheValueJson)
|
||||
|
||||
if (cacheValue) {
|
||||
return cacheValue
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
remove(prefixedCacheKey)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a key that has not been prefixed, and a value that must be passable
|
||||
// as an argument to JSON.stringify().
|
||||
//
|
||||
// Prefixes the key, and stores the JSON-stringified value in localStorage.
|
||||
//
|
||||
// Always returns undefined.
|
||||
export function set(key, value) {
|
||||
if ('function' !== typeof window?.localStorage?.setItem) return
|
||||
|
||||
const prefixedCacheKey = buildPrefixedKey(key)
|
||||
|
||||
try {
|
||||
const valueJson = JSON.stringify(value)
|
||||
localStorage.setItem(prefixedCacheKey, valueJson)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
const reportWebVitals = onPerfEntry => {
|
||||
const reportWebVitals = (onPerfEntry) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
getCLS(onPerfEntry)
|
||||
getFID(onPerfEntry)
|
||||
getFCP(onPerfEntry)
|
||||
getLCP(onPerfEntry)
|
||||
getTTFB(onPerfEntry)
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default reportWebVitals;
|
||||
export default reportWebVitals
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
import Enzyme from 'enzyme'
|
||||
import Adapter from 'enzyme-adapter-react-16'
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import { insertBlock, createNewPost, enablePageDialogAccept, pressKeyTimes, clickBlockToolbarButton, getAllBlocks, loginUser } from '@wordpress/e2e-test-utils'
|
||||
|
||||
jest.setTimeout(15000)
|
||||
|
||||
let config = null
|
||||
|
||||
describe('blockEditorIconChooser', () => {
|
||||
beforeAll(async () => {
|
||||
enablePageDialogAccept()
|
||||
await loginUser(process.env.WP_USERNAME, process.env.WP_PASSWORD)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await createNewPost()
|
||||
config = await page.evaluate(() => window.__FontAwesomeOfficialPlugin__)
|
||||
})
|
||||
|
||||
test('works', async () => {
|
||||
await insertBlock( 'Paragraph' )
|
||||
await pressKeyTimes('Space', 1)
|
||||
await clickBlockToolbarButton('More')
|
||||
await pressKeyTimes('Enter', 1)
|
||||
|
||||
const searchInput = await page.waitForFunction(() => {
|
||||
const chooser = document.querySelector('fa-icon-chooser')
|
||||
const shadowRoot = chooser && chooser.shadowRoot
|
||||
return shadowRoot.querySelector('input#search')
|
||||
})
|
||||
|
||||
await searchInput.focus()
|
||||
await searchInput.type('coffee', { delay: 100 })
|
||||
|
||||
await page.waitForResponse(
|
||||
`${config.apiUrl}/api?_locale=user`
|
||||
);
|
||||
|
||||
const firstIcon = await page.waitForFunction(() => {
|
||||
return document.querySelector('fa-icon-chooser').shadowRoot.querySelector('button.icon')
|
||||
})
|
||||
|
||||
await firstIcon.click()
|
||||
|
||||
const blocks = await getAllBlocks()
|
||||
expect(blocks).toHaveLength(1)
|
||||
expect(blocks[0].attributes.content).toMatch(/\[icon.*?\]$/)
|
||||
})
|
||||
})
|
|
@ -1,32 +0,0 @@
|
|||
import { visitAdminPage } from '@wordpress/e2e-test-utils'
|
||||
import { resetOptions } from '../testUtil'
|
||||
|
||||
describe('changeTechnology', () => {
|
||||
beforeAll(async () => {
|
||||
await visitAdminPage('options-general.php', 'page=font-awesome')
|
||||
await resetOptions(page)
|
||||
await page.reload()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await resetOptions(page)
|
||||
})
|
||||
|
||||
test('works', async () => {
|
||||
const useCdnInput = await page.$('#select_use_cdn')
|
||||
const useKitInput = await page.$('#select_use_kits')
|
||||
|
||||
expect(await useCdnInput.evaluate( i => i.checked )).toBe(true)
|
||||
expect(await useKitInput.evaluate( i => i.checked )).toBe(false)
|
||||
|
||||
const techSvgInput = await page.$('#code_edit_tech_svg')
|
||||
expect(await techSvgInput.evaluate( i => i.checked )).toBe(false)
|
||||
await techSvgInput.click()
|
||||
|
||||
const submitBefore = await page.waitForSelector('#submit:enabled')
|
||||
await submitBefore.click()
|
||||
|
||||
await page.waitForSelector('#submit:disabled')
|
||||
expect(await techSvgInput.evaluate( i => i.checked )).toBe(true)
|
||||
})
|
||||
})
|
|
@ -1,12 +1,9 @@
|
|||
import axios from 'axios'
|
||||
import toPairs from 'lodash/toPairs'
|
||||
import size from 'lodash/size'
|
||||
import get from 'lodash/get'
|
||||
import find from 'lodash/find'
|
||||
import { toPairs, size, get, has, find } from 'lodash'
|
||||
import reportRequestError, { redactRequestData, redactHeaders } from '../util/reportRequestError'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import has from 'lodash/has'
|
||||
import sliceJson from '../util/sliceJson'
|
||||
import { clearQueryCache } from '../queryCache'
|
||||
|
||||
const restApiAxios = axios.create()
|
||||
|
||||
|
@ -20,35 +17,36 @@ export const CONFLICT_DETECTION_SCANNER_DURATION_MIN = 10
|
|||
// (which would just be exactly "now").
|
||||
const CONFLICT_DETECTION_SCANNER_DEACTIVATION_DELTA_MS = 1
|
||||
|
||||
const COULD_NOT_SAVE_CHANGES_MESSAGE = __( 'Couldn\'t save those changes', 'font-awesome' )
|
||||
const REJECTED_METHOD_COULD_NOT_SAVE_CHANGES_MESSAGE = __( 'Changes not saved because your WordPress server does not allow this kind of request. Look for details in the browser console.', 'font-awesome' )
|
||||
const COULD_NOT_CHECK_PREFERENCES_MESSAGE = __( 'Couldn\'t check preferences', 'font-awesome' )
|
||||
const NO_RESPONSE_MESSAGE = __( 'A request to your WordPress server never received a response', 'font-awesome' )
|
||||
const REQUEST_FAILED_MESSAGE = __( 'A request to your WordPress server failed', 'font-awesome' )
|
||||
const COULD_NOT_START_SCANNER_MESSAGE = __( 'Couldn\'t start the scanner', 'font-awesome' )
|
||||
const COULD_NOT_SNOOZE_MESSAGE = __( 'Couldn\'t snooze', 'font-awesome' )
|
||||
|
||||
export function preprocessResponse( response ) {
|
||||
const confirmed = has( response, 'headers.fontawesome-confirmation' )
|
||||
const COULD_NOT_SAVE_CHANGES_MESSAGE = __("Couldn't save those changes", 'font-awesome')
|
||||
const REJECTED_METHOD_COULD_NOT_SAVE_CHANGES_MESSAGE = __(
|
||||
'Changes not saved because your WordPress server does not allow this kind of request. Look for details in the browser console.',
|
||||
'font-awesome'
|
||||
)
|
||||
const COULD_NOT_CHECK_PREFERENCES_MESSAGE = __("Couldn't check preferences", 'font-awesome')
|
||||
const NO_RESPONSE_MESSAGE = __('A request to your WordPress server never received a response', 'font-awesome')
|
||||
const REQUEST_FAILED_MESSAGE = __('A request to your WordPress server failed', 'font-awesome')
|
||||
const COULD_NOT_START_SCANNER_MESSAGE = __("Couldn't start the scanner", 'font-awesome')
|
||||
const COULD_NOT_SNOOZE_MESSAGE = __("Couldn't snooze", 'font-awesome')
|
||||
|
||||
if ( 204 === response.status && '' !== response.data ) {
|
||||
reportRequestError({ error: null, confirmed, trimmed: response.data, expectEmpty: true })
|
||||
// clean it up
|
||||
response.data = {}
|
||||
return response
|
||||
export function preprocessResponse(response) {
|
||||
const confirmed = has(response, 'headers.fontawesome-confirmation')
|
||||
|
||||
if (204 === response.status && '' !== response.data) {
|
||||
reportRequestError({ error: null, confirmed, trimmed: response.data, expectEmpty: true })
|
||||
// clean it up
|
||||
response.data = {}
|
||||
return response
|
||||
}
|
||||
|
||||
const data = get(response, 'data', null)
|
||||
|
||||
const foundUnexpectedData = 'string' === typeof data && size(data) > 0
|
||||
|
||||
const sliced = foundUnexpectedData
|
||||
? sliceJson( data )
|
||||
: {}
|
||||
|
||||
const sliced = foundUnexpectedData ? sliceJson(data) : {}
|
||||
|
||||
// Fixup the response data if garbage was fixed
|
||||
if ( foundUnexpectedData) {
|
||||
if ( sliced ) {
|
||||
if (foundUnexpectedData) {
|
||||
if (sliced) {
|
||||
response.data = get(sliced, 'parsed')
|
||||
}
|
||||
}
|
||||
|
@ -56,10 +54,10 @@ export function preprocessResponse( response ) {
|
|||
// If we had to trim any garbage, we'll store it here
|
||||
const trimmed = get(sliced, 'trimmed', '')
|
||||
|
||||
const errors = get( response, 'data.errors', null )
|
||||
const errors = get(response, 'data.errors', null)
|
||||
|
||||
if ( response.status >= 400 ) {
|
||||
if ( errors ) {
|
||||
if (response.status >= 400) {
|
||||
if (errors) {
|
||||
// This is just a normal error response.
|
||||
response.uiMessage = reportRequestError({ error: response.data, confirmed, trimmed })
|
||||
} else {
|
||||
|
@ -97,8 +95,8 @@ export function preprocessResponse( response ) {
|
|||
* through, unless we can see that the response has been corrupted,
|
||||
* in which case we'll report that first.
|
||||
*/
|
||||
if ( response.status < 400 && response.status >= 300 ) {
|
||||
if ( !confirmed || '' !== trimmed ) {
|
||||
if (response.status < 400 && response.status >= 300) {
|
||||
if (!confirmed || '' !== trimmed) {
|
||||
response.uiMessage = reportRequestError({ error: null, confirmed, trimmed })
|
||||
}
|
||||
|
||||
|
@ -113,8 +111,8 @@ export function preprocessResponse( response ) {
|
|||
* or cases where it's legitmate for the controller to return an otherwise
|
||||
* successful response that also includes some error data for extra diagnostics.
|
||||
*/
|
||||
if ( errors ) {
|
||||
/**
|
||||
if (errors) {
|
||||
/**
|
||||
* The controller sent back _only_ error data, though the HTTP status is 2XX.
|
||||
* This is a false positive.
|
||||
* This can occur when other buggy code running on the WordPress server preempts
|
||||
|
@ -126,9 +124,9 @@ export function preprocessResponse( response ) {
|
|||
response.uiMessage = reportRequestError({ error: response.data, confirmed, falsePositive, trimmed })
|
||||
return response
|
||||
} else {
|
||||
const error = get( response, 'data.error', null )
|
||||
const error = get(response, 'data.error', null)
|
||||
|
||||
if( error ) {
|
||||
if (error) {
|
||||
/**
|
||||
* We may receive errors back with a 200 success response, such as when
|
||||
* the controller catches PreferenceRegistrationExceptions.
|
||||
|
@ -137,7 +135,7 @@ export function preprocessResponse( response ) {
|
|||
return response
|
||||
}
|
||||
|
||||
if( !confirmed ) {
|
||||
if (!confirmed) {
|
||||
/**
|
||||
* We have received a response that, by every indication so far, is successful.
|
||||
* However, it lacks the confirmation header, which _might_ indicate a problem.
|
||||
|
@ -149,16 +147,16 @@ export function preprocessResponse( response ) {
|
|||
}
|
||||
|
||||
restApiAxios.interceptors.response.use(
|
||||
response => preprocessResponse( response ),
|
||||
error => {
|
||||
if( error.response ) {
|
||||
error.response = preprocessResponse( error.response )
|
||||
(response) => preprocessResponse(response),
|
||||
(error) => {
|
||||
if (error.response) {
|
||||
error.response = preprocessResponse(error.response)
|
||||
error.uiMessage = get(error, 'response.uiMessage')
|
||||
} else if ( error.request ) {
|
||||
} else if (error.request) {
|
||||
const code = 'fontawesome_request_noresponse'
|
||||
const e = {
|
||||
errors: {
|
||||
[code]: [ NO_RESPONSE_MESSAGE ]
|
||||
[code]: [NO_RESPONSE_MESSAGE]
|
||||
},
|
||||
error_data: {
|
||||
[code]: { request: error.request }
|
||||
|
@ -170,7 +168,7 @@ restApiAxios.interceptors.response.use(
|
|||
const code = 'fontawesome_request_failed'
|
||||
const e = {
|
||||
errors: {
|
||||
[code]: [ REQUEST_FAILED_MESSAGE ]
|
||||
[code]: [REQUEST_FAILED_MESSAGE]
|
||||
},
|
||||
error_data: {
|
||||
[code]: { failedRequestMessage: error.message }
|
||||
|
@ -197,22 +195,22 @@ export function resetOptionsFormState() {
|
|||
}
|
||||
|
||||
export function addPendingOption(change) {
|
||||
return function(dispatch, getState) {
|
||||
return function (dispatch, getState) {
|
||||
const { options } = getState()
|
||||
|
||||
for (const [ key, val ] of toPairs(change)) {
|
||||
for (const [key, val] of toPairs(change)) {
|
||||
const originalValue = options[key]
|
||||
|
||||
// If we're changing back to an original setting
|
||||
if( originalValue === val ) {
|
||||
if (originalValue === val) {
|
||||
dispatch({
|
||||
type: 'RESET_PENDING_OPTION',
|
||||
change: {[key]: val}
|
||||
change: { [key]: val }
|
||||
})
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'ADD_PENDING_OPTION',
|
||||
change: {[key]: val}
|
||||
change: { [key]: val }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -239,11 +237,11 @@ export function resetPendingBlocklistSubmissionStatus() {
|
|||
}
|
||||
|
||||
export function submitPendingUnregisteredClientDeletions() {
|
||||
return function(dispatch, getState){
|
||||
return function (dispatch, getState) {
|
||||
const { apiNonce, apiUrl, unregisteredClientsDeletionStatus } = getState()
|
||||
const deleteList = get( unregisteredClientsDeletionStatus, 'pending', null )
|
||||
const deleteList = get(unregisteredClientsDeletionStatus, 'pending', null)
|
||||
|
||||
if (!deleteList || size( deleteList ) === 0) return
|
||||
if (!deleteList || size(deleteList) === 0) return
|
||||
|
||||
dispatch({ type: 'DELETE_UNREGISTERED_CLIENTS_START' })
|
||||
|
||||
|
@ -255,28 +253,28 @@ export function submitPendingUnregisteredClientDeletions() {
|
|||
})
|
||||
}
|
||||
|
||||
return restApiAxios.delete(
|
||||
`${apiUrl}/conflict-detection/conflicts`,
|
||||
{
|
||||
return restApiAxios
|
||||
.delete(`${apiUrl}/conflict-detection/conflicts`, {
|
||||
data: deleteList,
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
const { status, data, falsePositive } = response
|
||||
})
|
||||
.then((response) => {
|
||||
const { status, data, falsePositive } = response
|
||||
|
||||
if ( falsePositive ) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'DELETE_UNREGISTERED_CLIENTS_END',
|
||||
success: true,
|
||||
data: 204 === status ? null : data,
|
||||
message: ''
|
||||
})
|
||||
}
|
||||
}).catch(handleError)
|
||||
if (falsePositive) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'DELETE_UNREGISTERED_CLIENTS_END',
|
||||
success: true,
|
||||
data: 204 === status ? null : data,
|
||||
message: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,13 +286,13 @@ export function updatePendingBlocklist(data = []) {
|
|||
}
|
||||
|
||||
export function submitPendingBlocklist() {
|
||||
return function(dispatch, getState){
|
||||
return function (dispatch, getState) {
|
||||
const { apiNonce, apiUrl, blocklistUpdateStatus } = getState()
|
||||
const blocklist = get( blocklistUpdateStatus, 'pending', null )
|
||||
const blocklist = get(blocklistUpdateStatus, 'pending', null)
|
||||
|
||||
if (!blocklist) return
|
||||
|
||||
dispatch({type: 'BLOCKLIST_UPDATE_START'})
|
||||
dispatch({ type: 'BLOCKLIST_UPDATE_START' })
|
||||
|
||||
const handleError = ({ uiMessage }) => {
|
||||
dispatch({
|
||||
|
@ -304,34 +302,33 @@ export function submitPendingBlocklist() {
|
|||
})
|
||||
}
|
||||
|
||||
return restApiAxios.put(
|
||||
`${apiUrl}/conflict-detection/conflicts/blocklist`,
|
||||
blocklist,
|
||||
{
|
||||
return restApiAxios
|
||||
.post(`${apiUrl}/conflict-detection/conflicts/blocklist`, blocklist, {
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
const { status, data, falsePositive } = response
|
||||
})
|
||||
.then((response) => {
|
||||
const { status, data, falsePositive } = response
|
||||
|
||||
if ( falsePositive ) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'BLOCKLIST_UPDATE_END',
|
||||
success: true,
|
||||
data: 204 === status ? null : data,
|
||||
message: ''
|
||||
})
|
||||
}
|
||||
}).catch(handleError)
|
||||
if (falsePositive) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'BLOCKLIST_UPDATE_END',
|
||||
success: true,
|
||||
data: 204 === status ? null : data,
|
||||
message: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
export function checkPreferenceConflicts() {
|
||||
return function(dispatch, getState){
|
||||
dispatch({type: 'PREFERENCE_CHECK_START'})
|
||||
return function (dispatch, getState) {
|
||||
dispatch({ type: 'PREFERENCE_CHECK_START' })
|
||||
const { apiNonce, apiUrl, options, pendingOptions } = getState()
|
||||
|
||||
const handleError = ({ uiMessage }) => {
|
||||
|
@ -342,39 +339,42 @@ export function checkPreferenceConflicts() {
|
|||
})
|
||||
}
|
||||
|
||||
return restApiAxios.post(
|
||||
`${apiUrl}/preference-check`,
|
||||
{ ...options, ...pendingOptions },
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
return restApiAxios
|
||||
.post(
|
||||
`${apiUrl}/preference-check`,
|
||||
{ ...options, ...pendingOptions },
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
const { data, falsePositive } = response
|
||||
)
|
||||
.then((response) => {
|
||||
const { data, falsePositive } = response
|
||||
|
||||
if( falsePositive ) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'PREFERENCE_CHECK_END',
|
||||
success: true,
|
||||
message: '',
|
||||
detectedConflicts: data
|
||||
})
|
||||
}
|
||||
}).catch(handleError)
|
||||
if (falsePositive) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'PREFERENCE_CHECK_END',
|
||||
success: true,
|
||||
message: '',
|
||||
detectedConflicts: data
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
export function chooseAwayFromKitConfig({ activeKitToken }) {
|
||||
return function(dispatch, getState) {
|
||||
return function (dispatch, getState) {
|
||||
const { releases } = getState()
|
||||
|
||||
dispatch({
|
||||
type: 'CHOOSE_AWAY_FROM_KIT_CONFIG',
|
||||
activeKitToken,
|
||||
concreteVersion: get(releases, 'latest_version_6')
|
||||
concreteVersion: get(releases, 'latest_version_7')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -384,18 +384,20 @@ export function chooseIntoKitConfig() {
|
|||
}
|
||||
|
||||
export function queryKits() {
|
||||
return function(dispatch, getState) {
|
||||
return function (dispatch, getState) {
|
||||
const { apiNonce, apiUrl, options } = getState()
|
||||
|
||||
const initialKitToken = get(options, 'kitToken', null)
|
||||
|
||||
dispatch({ type: 'KITS_QUERY_START' })
|
||||
|
||||
clearQueryCache()
|
||||
|
||||
const handleKitsQueryError = ({ uiMessage }) => {
|
||||
dispatch({
|
||||
type: 'KITS_QUERY_END',
|
||||
success: false,
|
||||
message: uiMessage || __( 'Failed to fetch kits', 'font-awesome' )
|
||||
message: uiMessage || __('Failed to fetch kits', 'font-awesome')
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -403,167 +405,121 @@ export function queryKits() {
|
|||
dispatch({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: false,
|
||||
message: uiMessage || __( 'Couldn\'t update latest kit settings', 'font-awesome' )
|
||||
message: uiMessage || __("Couldn't update latest kit settings", 'font-awesome')
|
||||
})
|
||||
}
|
||||
|
||||
return restApiAxios.post(
|
||||
`${apiUrl}/api`,
|
||||
`query {
|
||||
me {
|
||||
kits {
|
||||
name
|
||||
version
|
||||
technologySelected
|
||||
licenseSelected
|
||||
minified
|
||||
token
|
||||
shimEnabled
|
||||
autoAccessibilityEnabled
|
||||
status
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
if ( response.falsePositive ) return handleKitsQueryError(response)
|
||||
|
||||
const data = get(response, 'data.data')
|
||||
|
||||
// We may receive errors back with a 200 response, such as when
|
||||
// there PreferenceRegistrationExceptions.
|
||||
if( get( data, 'me') ) {
|
||||
dispatch({
|
||||
type: 'KITS_QUERY_END',
|
||||
data,
|
||||
success: true
|
||||
})
|
||||
} else {
|
||||
return dispatch({
|
||||
type: 'KITS_QUERY_END',
|
||||
success: false,
|
||||
message: __( 'Failed to fetch kits. Regenerate your API Token and try again.', 'font-awesome' )
|
||||
})
|
||||
}
|
||||
|
||||
// If we didn't start out with a saved kitToken, we're done.
|
||||
// Otherwise, we'll move on to update any config on that kit which
|
||||
// might have changed since we saved it in WordPress.
|
||||
if(! initialKitToken) return
|
||||
|
||||
const refreshedKits = get( data, 'me.kits', [] )
|
||||
const currentKitRefreshed = find( refreshedKits, { token: initialKitToken } )
|
||||
|
||||
if(! currentKitRefreshed) return
|
||||
|
||||
const optionsUpdate = {}
|
||||
|
||||
// Inspect each relevant kit option for the current kit to see if it's
|
||||
// been changed since our last query.
|
||||
if( options.usePro && currentKitRefreshed.licenseSelected !== 'pro' ) {
|
||||
optionsUpdate.usePro = false
|
||||
} else if ( !options.usePro && currentKitRefreshed.licenseSelected === 'pro' ) {
|
||||
optionsUpdate.usePro = true
|
||||
}
|
||||
|
||||
if( options.technology === 'svg' && currentKitRefreshed.technologySelected !== 'svg' ) {
|
||||
optionsUpdate.technology = 'webfont'
|
||||
// pseudoElements must always be true for webfont
|
||||
optionsUpdate.pseudoElements = true
|
||||
} else if( options.technology !== 'svg' && currentKitRefreshed.technologySelected === 'svg' ) {
|
||||
optionsUpdate.technology = 'svg'
|
||||
// pseudoElements must always be false for svg when loaded in a kit
|
||||
optionsUpdate.pseudoElements = false
|
||||
}
|
||||
|
||||
if( options.version !== currentKitRefreshed.version) {
|
||||
optionsUpdate.version = currentKitRefreshed.version
|
||||
}
|
||||
|
||||
if( options.compat && !currentKitRefreshed.shimEnabled ) {
|
||||
optionsUpdate.compat = false
|
||||
} else if( !options.compat && currentKitRefreshed.shimEnabled ) {
|
||||
optionsUpdate.compat = true
|
||||
}
|
||||
|
||||
dispatch({type: 'OPTIONS_FORM_SUBMIT_START'})
|
||||
|
||||
return restApiAxios.put(
|
||||
`${apiUrl}/config`,
|
||||
{
|
||||
options: {
|
||||
...options, ...optionsUpdate
|
||||
}
|
||||
},
|
||||
return restApiAxios
|
||||
.post(
|
||||
`${apiUrl}/api`,
|
||||
'query { me { kits { name version technologySelected licenseSelected minified token shimEnabled autoAccessibilityEnabled status }}}',
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
const { data, falsePositive } = response
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.falsePositive) return handleKitsQueryError(response)
|
||||
|
||||
if ( falsePositive ) return handleKitUpdateError(response)
|
||||
const data = get(response, 'data.data')
|
||||
|
||||
dispatch({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
data,
|
||||
success: true,
|
||||
message: __( 'Kit changes saved', 'font-awesome' )
|
||||
})
|
||||
}).catch(handleKitUpdateError)
|
||||
}).catch(handleKitsQueryError)
|
||||
// We may receive errors back with a 200 response, such as when
|
||||
// there PreferenceRegistrationExceptions.
|
||||
if (get(data, 'me')) {
|
||||
dispatch({
|
||||
type: 'KITS_QUERY_END',
|
||||
data,
|
||||
success: true
|
||||
})
|
||||
} else {
|
||||
return dispatch({
|
||||
type: 'KITS_QUERY_END',
|
||||
success: false,
|
||||
message: __('Failed to fetch kits. Regenerate your API Token and try again.', 'font-awesome')
|
||||
})
|
||||
}
|
||||
|
||||
// If we didn't start out with a saved kitToken, we're done.
|
||||
// Otherwise, we'll move on to update any config on that kit which
|
||||
// might have changed since we saved it in WordPress.
|
||||
if (!initialKitToken) return
|
||||
|
||||
const refreshedKits = get(data, 'me.kits', [])
|
||||
const currentKitRefreshed = find(refreshedKits, { token: initialKitToken })
|
||||
|
||||
if (!currentKitRefreshed) return
|
||||
|
||||
const optionsUpdate = {}
|
||||
|
||||
// Inspect each relevant kit option for the current kit to see if it's
|
||||
// been changed since our last query.
|
||||
if (options.usePro && currentKitRefreshed.licenseSelected !== 'pro') {
|
||||
optionsUpdate.usePro = false
|
||||
} else if (!options.usePro && currentKitRefreshed.licenseSelected === 'pro') {
|
||||
optionsUpdate.usePro = true
|
||||
}
|
||||
|
||||
if (options.technology === 'svg' && currentKitRefreshed.technologySelected !== 'svg') {
|
||||
optionsUpdate.technology = 'webfont'
|
||||
// pseudoElements must always be true for webfont
|
||||
optionsUpdate.pseudoElements = true
|
||||
} else if (options.technology !== 'svg' && currentKitRefreshed.technologySelected === 'svg') {
|
||||
optionsUpdate.technology = 'svg'
|
||||
// pseudoElements must always be false for svg when loaded in a kit
|
||||
optionsUpdate.pseudoElements = false
|
||||
}
|
||||
|
||||
if (options.version !== currentKitRefreshed.version) {
|
||||
optionsUpdate.version = currentKitRefreshed.version
|
||||
}
|
||||
|
||||
if (options.compat && !currentKitRefreshed.shimEnabled) {
|
||||
optionsUpdate.compat = false
|
||||
} else if (!options.compat && currentKitRefreshed.shimEnabled) {
|
||||
optionsUpdate.compat = true
|
||||
}
|
||||
|
||||
dispatch({ type: 'OPTIONS_FORM_SUBMIT_START' })
|
||||
|
||||
return restApiAxios
|
||||
.post(
|
||||
`${apiUrl}/config`,
|
||||
{
|
||||
options: {
|
||||
...options,
|
||||
...optionsUpdate
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
const { data, falsePositive } = response
|
||||
|
||||
if (falsePositive) return handleKitUpdateError(response)
|
||||
|
||||
dispatch({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
data,
|
||||
success: true,
|
||||
message: __('Kit changes saved', 'font-awesome')
|
||||
})
|
||||
})
|
||||
.catch(handleKitUpdateError)
|
||||
})
|
||||
.catch(handleKitsQueryError)
|
||||
}
|
||||
}
|
||||
|
||||
export function submitPendingOptions() {
|
||||
return function(dispatch, getState) {
|
||||
return function (dispatch, getState) {
|
||||
const { apiNonce, apiUrl, options, pendingOptions } = getState()
|
||||
|
||||
dispatch({type: 'OPTIONS_FORM_SUBMIT_START'})
|
||||
|
||||
const handleError = ({ uiMessage }) => {
|
||||
dispatch({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: false,
|
||||
message: uiMessage || COULD_NOT_SAVE_CHANGES_MESSAGE
|
||||
})
|
||||
}
|
||||
|
||||
return restApiAxios.put(
|
||||
`${apiUrl}/config`,
|
||||
{ options: { ...options, ...pendingOptions }},
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
const { data, falsePositive } = response
|
||||
|
||||
if ( falsePositive ) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
data,
|
||||
success: true,
|
||||
message: __( 'Changes saved', 'font-awesome' )
|
||||
})
|
||||
}
|
||||
}).catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
export function updateApiToken({ apiToken = false, runQueryKits = false }) {
|
||||
return function(dispatch, getState) {
|
||||
const { apiNonce, apiUrl, options } = getState()
|
||||
|
||||
dispatch({type: 'OPTIONS_FORM_SUBMIT_START'})
|
||||
dispatch({ type: 'OPTIONS_FORM_SUBMIT_START' })
|
||||
|
||||
const handleError = ({ uiMessage }) => {
|
||||
dispatch({
|
||||
|
@ -573,32 +529,77 @@ export function updateApiToken({ apiToken = false, runQueryKits = false }) {
|
|||
})
|
||||
}
|
||||
|
||||
return restApiAxios.put(
|
||||
`${apiUrl}/config`,
|
||||
{ options: { ...options, apiToken }},
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
return restApiAxios
|
||||
.post(
|
||||
`${apiUrl}/config`,
|
||||
{ options: { ...options, ...pendingOptions } },
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
const { data, falsePositive } = response
|
||||
)
|
||||
.then((response) => {
|
||||
const { data, falsePositive } = response
|
||||
|
||||
if ( falsePositive ) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
data,
|
||||
success: true,
|
||||
message: __( 'API Token saved', 'font-awesome' )
|
||||
})
|
||||
|
||||
if( runQueryKits ) {
|
||||
return dispatch(queryKits())
|
||||
if (falsePositive) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
data,
|
||||
success: true,
|
||||
message: __('Changes saved', 'font-awesome')
|
||||
})
|
||||
}
|
||||
}
|
||||
}).catch(handleError)
|
||||
})
|
||||
.catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
export function updateApiToken({ apiToken = false, runQueryKits = false }) {
|
||||
return function (dispatch, getState) {
|
||||
const { apiNonce, apiUrl, options } = getState()
|
||||
|
||||
dispatch({ type: 'OPTIONS_FORM_SUBMIT_START' })
|
||||
|
||||
const handleError = ({ uiMessage }) => {
|
||||
dispatch({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: false,
|
||||
message: uiMessage || COULD_NOT_SAVE_CHANGES_MESSAGE
|
||||
})
|
||||
}
|
||||
|
||||
return restApiAxios
|
||||
.post(
|
||||
`${apiUrl}/config`,
|
||||
{ options: { ...options, apiToken } },
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
const { data, falsePositive } = response
|
||||
|
||||
if (falsePositive) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
data,
|
||||
success: true,
|
||||
message: __('API Token saved', 'font-awesome')
|
||||
})
|
||||
|
||||
if (runQueryKits) {
|
||||
return dispatch(queryKits())
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -617,12 +618,12 @@ export function reportDetectedConflicts({ nodesTested = {} }) {
|
|||
// the current page's scan was complete and report submitted. In that case,
|
||||
// we just ignore the report. Otherwise, this action would try to post results
|
||||
// to a REST route that will no longer be registered and listening, resulting a 404.
|
||||
if( !showConflictDetectionReporter ) {
|
||||
if (!showConflictDetectionReporter) {
|
||||
return
|
||||
}
|
||||
|
||||
if( size(nodesTested.conflict) > 0 ) {
|
||||
const payload = Object.keys(nodesTested.conflict).reduce(function(acc, md5){
|
||||
if (size(nodesTested.conflict) > 0) {
|
||||
const payload = Object.keys(nodesTested.conflict).reduce(function (acc, md5) {
|
||||
acc[md5] = nodesTested.conflict[md5]
|
||||
return acc
|
||||
}, {})
|
||||
|
@ -641,81 +642,37 @@ export function reportDetectedConflicts({ nodesTested = {} }) {
|
|||
})
|
||||
}
|
||||
|
||||
return restApiAxios.post(
|
||||
`${apiUrl}/conflict-detection/conflicts`,
|
||||
payload,
|
||||
{
|
||||
return restApiAxios
|
||||
.post(`${apiUrl}/conflict-detection/conflicts`, payload, {
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
const { status, data, falsePositive } = response
|
||||
})
|
||||
.then((response) => {
|
||||
const { status, data, falsePositive } = response
|
||||
|
||||
if ( falsePositive ) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'CONFLICT_DETECTION_SUBMIT_END',
|
||||
success: true,
|
||||
/**
|
||||
* If get back no data here, that can only mean that a previous
|
||||
* response with garbage in it had an erroneous HTTP 200 status
|
||||
* on it, but no parseable JSON, which is equivalent to a 204.
|
||||
*/
|
||||
data: ( 204 === status || 0 === size(data) ) ? null : data
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(handleError)
|
||||
if (falsePositive) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'CONFLICT_DETECTION_SUBMIT_END',
|
||||
success: true,
|
||||
/**
|
||||
* If get back no data here, that can only mean that a previous
|
||||
* response with garbage in it had an erroneous HTTP 200 status
|
||||
* on it, but no parseable JSON, which is equivalent to a 204.
|
||||
*/
|
||||
data: 204 === status || 0 === size(data) ? null : data
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(handleError)
|
||||
} else {
|
||||
dispatch({ type: 'CONFLICT_DETECTION_NONE_FOUND' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function snoozeV3DeprecationWarning() {
|
||||
return (dispatch, getState) => {
|
||||
const { apiNonce, apiUrl } = getState()
|
||||
|
||||
dispatch({ type: 'SNOOZE_V3DEPRECATION_WARNING_START' })
|
||||
|
||||
const handleError = ({ uiMessage }) => {
|
||||
dispatch({
|
||||
type: 'SNOOZE_V3DEPRECATION_WARNING_END',
|
||||
success: false,
|
||||
message: uiMessage || COULD_NOT_SNOOZE_MESSAGE
|
||||
})
|
||||
}
|
||||
|
||||
return restApiAxios.put(
|
||||
`${apiUrl}/v3deprecation`,
|
||||
{ snooze: true },
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
const { falsePositive } = response
|
||||
|
||||
if ( falsePositive ) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'SNOOZE_V3DEPRECATION_WARNING_END',
|
||||
success: true,
|
||||
snooze: true,
|
||||
message: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
export function setActiveAdminTab(tab) {
|
||||
return {
|
||||
type: 'SET_ACTIVE_ADMIN_TAB',
|
||||
|
@ -724,18 +681,14 @@ export function setActiveAdminTab(tab) {
|
|||
}
|
||||
|
||||
export function setConflictDetectionScanner({ enable = true }) {
|
||||
return function(dispatch, getState) {
|
||||
return function (dispatch, getState) {
|
||||
const { apiNonce, apiUrl } = getState()
|
||||
|
||||
const actionStartType = enable
|
||||
? 'ENABLE_CONFLICT_DETECTION_SCANNER_START'
|
||||
: 'DISABLE_CONFLICT_DETECTION_SCANNER_START'
|
||||
const actionStartType = enable ? 'ENABLE_CONFLICT_DETECTION_SCANNER_START' : 'DISABLE_CONFLICT_DETECTION_SCANNER_START'
|
||||
|
||||
const actionEndType = enable
|
||||
? 'ENABLE_CONFLICT_DETECTION_SCANNER_END'
|
||||
: 'DISABLE_CONFLICT_DETECTION_SCANNER_END'
|
||||
const actionEndType = enable ? 'ENABLE_CONFLICT_DETECTION_SCANNER_END' : 'DISABLE_CONFLICT_DETECTION_SCANNER_END'
|
||||
|
||||
dispatch({type: actionStartType})
|
||||
dispatch({ type: actionStartType })
|
||||
|
||||
const handleError = ({ uiMessage }) => {
|
||||
dispatch({
|
||||
|
@ -745,28 +698,31 @@ export function setConflictDetectionScanner({ enable = true }) {
|
|||
})
|
||||
}
|
||||
|
||||
return restApiAxios.put(
|
||||
`${apiUrl}/conflict-detection/until`,
|
||||
enable
|
||||
? Math.floor((new Date((new Date()).valueOf() + (CONFLICT_DETECTION_SCANNER_DURATION_MIN * 1000 * 60))) / 1000)
|
||||
: Math.floor((new Date())/1000) - CONFLICT_DETECTION_SCANNER_DEACTIVATION_DELTA_MS,
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
return restApiAxios
|
||||
.post(
|
||||
`${apiUrl}/conflict-detection/until`,
|
||||
enable
|
||||
? Math.floor(new Date(new Date().valueOf() + CONFLICT_DETECTION_SCANNER_DURATION_MIN * 1000 * 60) / 1000)
|
||||
: Math.floor(new Date() / 1000) - CONFLICT_DETECTION_SCANNER_DEACTIVATION_DELTA_MS,
|
||||
{
|
||||
headers: {
|
||||
'X-WP-Nonce': apiNonce
|
||||
}
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
const { status, data, falsePositive } = response
|
||||
)
|
||||
.then((response) => {
|
||||
const { status, data, falsePositive } = response
|
||||
|
||||
if ( falsePositive ) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: actionEndType,
|
||||
data: 204 === status ? null : data,
|
||||
success: true
|
||||
})
|
||||
}
|
||||
}).catch(handleError)
|
||||
if (falsePositive) {
|
||||
handleError(response)
|
||||
} else {
|
||||
dispatch({
|
||||
type: actionEndType,
|
||||
data: 204 === status ? null : data,
|
||||
success: true
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(handleError)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { respondWith, resetAxiosMocks, changeImpl } from 'axios'
|
|||
import * as actions from './actions'
|
||||
import { submitPendingOptions, addPendingOption } from './actions'
|
||||
import configureMockStore from 'redux-mock-store'
|
||||
import thunk from 'redux-thunk'
|
||||
import { thunk } from 'redux-thunk'
|
||||
import reportRequestError, { MOCK_UI_MESSAGE, redactHeaders, redactRequestData } from '../util/reportRequestError'
|
||||
jest.mock('../util/reportRequestError')
|
||||
const apiUrl = '/font-awesome/v1'
|
||||
|
@ -30,14 +30,18 @@ describe('addPendingOption', () => {
|
|||
test('when multiple pending options are adjusted together, all are updated', () => {
|
||||
store.dispatch(addPendingOption({ technology: 'webfont', pseudoElements: true }))
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()[0]).toEqual(expect.objectContaining({
|
||||
type: 'ADD_PENDING_OPTION',
|
||||
change: { technology: 'webfont' }
|
||||
}))
|
||||
expect(store.getActions()[1]).toEqual(expect.objectContaining({
|
||||
type: 'ADD_PENDING_OPTION',
|
||||
change: { pseudoElements: true }
|
||||
}))
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: 'ADD_PENDING_OPTION',
|
||||
change: { technology: 'webfont' }
|
||||
})
|
||||
)
|
||||
expect(store.getActions()[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: 'ADD_PENDING_OPTION',
|
||||
change: { pseudoElements: true }
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -63,7 +67,7 @@ describe('submitPendingOptions and interceptors', () => {
|
|||
afterEach(() => {
|
||||
resetAxiosMocks()
|
||||
})
|
||||
|
||||
|
||||
describe('when HTTP 200', () => {
|
||||
describe('when confirmation header is present', () => {
|
||||
describe('successful JSON response also includes error information', () => {
|
||||
|
@ -74,11 +78,11 @@ describe('submitPendingOptions and interceptors', () => {
|
|||
options: pendingOptions,
|
||||
error: {
|
||||
errors: {
|
||||
"code1": ["message1"],
|
||||
code1: ['message1']
|
||||
},
|
||||
error_data: {
|
||||
"code1": {
|
||||
"trace": 'some stack trace'
|
||||
code1: {
|
||||
trace: 'some stack trace'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +90,7 @@ describe('submitPendingOptions and interceptors', () => {
|
|||
|
||||
respondWith({
|
||||
url: `${apiUrl}/config`,
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
response: {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
|
@ -98,36 +102,42 @@ describe('submitPendingOptions and interceptors', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('submits successfully with successful ui message and also reports error to console', done => {
|
||||
store.dispatch(submitPendingOptions()).then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
||||
error: expect.objectContaining({
|
||||
errors: {
|
||||
code1: expect.anything()
|
||||
},
|
||||
error_data: {
|
||||
code1: expect.anything()
|
||||
}
|
||||
}),
|
||||
confirmed: true,
|
||||
ok: true
|
||||
}))
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: true,
|
||||
data,
|
||||
message: expect.stringContaining('saved')
|
||||
})
|
||||
]))
|
||||
done()
|
||||
})
|
||||
.catch(e => done(e))
|
||||
test('submits successfully with successful ui message and also reports error to console', (done) => {
|
||||
store
|
||||
.dispatch(submitPendingOptions())
|
||||
.then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
error: expect.objectContaining({
|
||||
errors: {
|
||||
code1: expect.anything()
|
||||
},
|
||||
error_data: {
|
||||
code1: expect.anything()
|
||||
}
|
||||
}),
|
||||
confirmed: true,
|
||||
ok: true
|
||||
})
|
||||
)
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: true,
|
||||
data,
|
||||
message: expect.stringContaining('saved')
|
||||
})
|
||||
])
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch((e) => done(e))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -143,7 +153,7 @@ describe('submitPendingOptions and interceptors', () => {
|
|||
|
||||
respondWith({
|
||||
url: `${apiUrl}/config`,
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
response: {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
|
@ -152,56 +162,64 @@ describe('submitPendingOptions and interceptors', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('reports warning but completes successfully', done => {
|
||||
store.dispatch(submitPendingOptions()).then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
||||
error: null,
|
||||
confirmed: false,
|
||||
ok: true,
|
||||
trimmed: INVALID_JSON_RESPONSE_DATA
|
||||
}))
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: true,
|
||||
data,
|
||||
message: expect.stringContaining('saved')
|
||||
})
|
||||
]))
|
||||
done()
|
||||
})
|
||||
.catch(e => done(e))
|
||||
})
|
||||
|
||||
describe('axios request', () => {
|
||||
let mockPut = null
|
||||
beforeEach(() => {
|
||||
mockPut = jest.fn(() => Promise.resolve({ data }))
|
||||
changeImpl({ name: 'put', fn: mockPut })
|
||||
})
|
||||
|
||||
test('submits pendingOptions', done => {
|
||||
store.dispatch(submitPendingOptions()).then(() => {
|
||||
expect(mockPut).toHaveBeenCalledTimes(1)
|
||||
expect(mockPut).toHaveBeenCalledWith(
|
||||
`${apiUrl}/config`,
|
||||
test('reports warning but completes successfully', (done) => {
|
||||
store
|
||||
.dispatch(submitPendingOptions())
|
||||
.then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: pendingOptions
|
||||
}),
|
||||
expect.objectContaining({
|
||||
headers: {
|
||||
'X-WP-Nonce': fakeNonce
|
||||
}
|
||||
error: null,
|
||||
confirmed: false,
|
||||
ok: true,
|
||||
trimmed: INVALID_JSON_RESPONSE_DATA
|
||||
})
|
||||
)
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: true,
|
||||
data,
|
||||
message: expect.stringContaining('saved')
|
||||
})
|
||||
])
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch(e => done(e))
|
||||
.catch((e) => done(e))
|
||||
})
|
||||
|
||||
describe('axios request', () => {
|
||||
let mockPost = null
|
||||
beforeEach(() => {
|
||||
mockPost = jest.fn(() => Promise.resolve({ data }))
|
||||
changeImpl({ name: 'post', fn: mockPost })
|
||||
})
|
||||
|
||||
test('submits pendingOptions', (done) => {
|
||||
store
|
||||
.dispatch(submitPendingOptions())
|
||||
.then(() => {
|
||||
expect(mockPost).toHaveBeenCalledTimes(1)
|
||||
expect(mockPost).toHaveBeenCalledWith(
|
||||
`${apiUrl}/config`,
|
||||
expect.objectContaining({
|
||||
options: pendingOptions
|
||||
}),
|
||||
expect.objectContaining({
|
||||
headers: {
|
||||
'X-WP-Nonce': fakeNonce
|
||||
}
|
||||
})
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch((e) => done(e))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -210,68 +228,74 @@ describe('submitPendingOptions and interceptors', () => {
|
|||
|
||||
describe('when HTTP 400', () => {
|
||||
describe('when errors payload is absent', () => {
|
||||
const responseData = {foo: 42}
|
||||
const url = `${apiUrl}/config`
|
||||
const method = 'PUT'
|
||||
const status = 400
|
||||
const statusText = 'Bad Request'
|
||||
const requestData = JSON.stringify({bar: 43})
|
||||
const responseHeaders = {
|
||||
'fontawesome-confirmation': 1
|
||||
}
|
||||
const requestHeaders = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
const responseData = { foo: 42 }
|
||||
const url = `${apiUrl}/config`
|
||||
const method = 'POST'
|
||||
const status = 400
|
||||
const statusText = 'Bad Request'
|
||||
const requestData = JSON.stringify({ bar: 43 })
|
||||
const responseHeaders = {
|
||||
'fontawesome-confirmation': 1
|
||||
}
|
||||
const requestHeaders = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
respondWith({
|
||||
url,
|
||||
method,
|
||||
response: {
|
||||
status,
|
||||
statusText,
|
||||
data: responseData,
|
||||
headers: responseHeaders,
|
||||
config: {
|
||||
method,
|
||||
url,
|
||||
data: requestData,
|
||||
headers: requestHeaders
|
||||
}
|
||||
},
|
||||
})
|
||||
beforeEach(() => {
|
||||
respondWith({
|
||||
url,
|
||||
method,
|
||||
response: {
|
||||
status,
|
||||
statusText,
|
||||
data: responseData,
|
||||
headers: responseHeaders,
|
||||
config: {
|
||||
method,
|
||||
url,
|
||||
data: requestData,
|
||||
headers: requestHeaders
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('displays default ui message and emits console message', done => {
|
||||
reportRequestError.mockReturnValueOnce(null)
|
||||
store.dispatch(submitPendingOptions()).then(() => {
|
||||
test('displays default ui message and emits console message', (done) => {
|
||||
reportRequestError.mockReturnValueOnce(null)
|
||||
store
|
||||
.dispatch(submitPendingOptions())
|
||||
.then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
||||
confirmed: true,
|
||||
requestMethod: method,
|
||||
//requestData,
|
||||
requestUrl: url,
|
||||
// responseHeaders: expect.any(Object),
|
||||
// requestHeaders: expect.any(Object),
|
||||
responseStatus: status,
|
||||
responseStatusText: statusText,
|
||||
responseData
|
||||
}))
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
||||
expect(reportRequestError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: false,
|
||||
message: expect.stringContaining("Couldn't save")
|
||||
confirmed: true,
|
||||
requestMethod: method,
|
||||
//requestData,
|
||||
requestUrl: url,
|
||||
// responseHeaders: expect.any(Object),
|
||||
// requestHeaders: expect.any(Object),
|
||||
responseStatus: status,
|
||||
responseStatusText: statusText,
|
||||
responseData
|
||||
})
|
||||
]))
|
||||
)
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: false,
|
||||
message: expect.stringContaining("Couldn't save")
|
||||
})
|
||||
])
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch(e => done(e))
|
||||
})
|
||||
.catch((e) => done(e))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -279,41 +303,47 @@ describe('submitPendingOptions and interceptors', () => {
|
|||
beforeEach(() => {
|
||||
respondWith({
|
||||
url: `${apiUrl}/config`,
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
response: new XMLHttpRequest()
|
||||
})
|
||||
|
||||
reportRequestError.mockImplementation(() => MOCK_UI_MESSAGE)
|
||||
})
|
||||
|
||||
test('failed request is reported to console and failure with uiMessage is dispatched to store', done => {
|
||||
store.dispatch(submitPendingOptions()).then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
||||
error: {
|
||||
errors: expect.objectContaining({
|
||||
fontawesome_request_noresponse: [ expect.any(String) ]
|
||||
}),
|
||||
error_data: {
|
||||
fontawesome_request_noresponse: {
|
||||
request: expect.any(XMLHttpRequest)
|
||||
test('failed request is reported to console and failure with uiMessage is dispatched to store', (done) => {
|
||||
store
|
||||
.dispatch(submitPendingOptions())
|
||||
.then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
error: {
|
||||
errors: expect.objectContaining({
|
||||
fontawesome_request_noresponse: [expect.any(String)]
|
||||
}),
|
||||
error_data: {
|
||||
fontawesome_request_noresponse: {
|
||||
request: expect.any(XMLHttpRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}))
|
||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: false,
|
||||
message: MOCK_UI_MESSAGE
|
||||
})
|
||||
]))
|
||||
done()
|
||||
})
|
||||
.catch(e => done(e))
|
||||
})
|
||||
)
|
||||
expect(store.getActions()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: false,
|
||||
message: MOCK_UI_MESSAGE
|
||||
})
|
||||
])
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch((e) => done(e))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -321,41 +351,47 @@ describe('submitPendingOptions and interceptors', () => {
|
|||
beforeEach(() => {
|
||||
respondWith({
|
||||
url: `${apiUrl}/config`,
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
response: new Error('some axios error')
|
||||
})
|
||||
|
||||
reportRequestError.mockImplementation(() => MOCK_UI_MESSAGE)
|
||||
})
|
||||
|
||||
test('failure is reported to console and failure with uiMessage is dispatched to store', done => {
|
||||
store.dispatch(submitPendingOptions()).then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
||||
error: {
|
||||
errors: expect.objectContaining({
|
||||
fontawesome_request_failed: [ expect.stringContaining('server failed') ]
|
||||
}),
|
||||
error_data: {
|
||||
fontawesome_request_failed: {
|
||||
failedRequestMessage: 'some axios error'
|
||||
test('failure is reported to console and failure with uiMessage is dispatched to store', (done) => {
|
||||
store
|
||||
.dispatch(submitPendingOptions())
|
||||
.then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
error: {
|
||||
errors: expect.objectContaining({
|
||||
fontawesome_request_failed: [expect.stringContaining('server failed')]
|
||||
}),
|
||||
error_data: {
|
||||
fontawesome_request_failed: {
|
||||
failedRequestMessage: 'some axios error'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}))
|
||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: false,
|
||||
message: MOCK_UI_MESSAGE
|
||||
})
|
||||
]))
|
||||
done()
|
||||
})
|
||||
.catch(e => done(e))
|
||||
})
|
||||
)
|
||||
expect(store.getActions()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||
success: false,
|
||||
message: MOCK_UI_MESSAGE
|
||||
})
|
||||
])
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch((e) => done(e))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -364,7 +400,6 @@ describe('some action failure cases', () => {
|
|||
const STATE_TECH_CHANGE = {
|
||||
options: {
|
||||
technology: 'webfont'
|
||||
|
||||
},
|
||||
pendingOptions: {
|
||||
technology: 'svg'
|
||||
|
@ -389,23 +424,23 @@ describe('some action failure cases', () => {
|
|||
action: 'updateApiToken',
|
||||
state: {},
|
||||
route: 'config',
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
startAction: 'OPTIONS_FORM_SUBMIT_START',
|
||||
endAction: 'OPTIONS_FORM_SUBMIT_END',
|
||||
params: {
|
||||
apiToken: 'xyz456',
|
||||
runQueryKits: false
|
||||
runQueryKits: false
|
||||
}
|
||||
},
|
||||
{
|
||||
action: 'submitPendingBlocklist',
|
||||
state: {
|
||||
blocklistUpdateStatus: {
|
||||
pending: [ 'abc123' ]
|
||||
pending: ['abc123']
|
||||
}
|
||||
},
|
||||
route: 'conflict-detection/conflicts/blocklist',
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
startAction: 'BLOCKLIST_UPDATE_START',
|
||||
endAction: 'BLOCKLIST_UPDATE_END',
|
||||
params: {}
|
||||
|
@ -414,7 +449,7 @@ describe('some action failure cases', () => {
|
|||
action: 'submitPendingUnregisteredClientDeletions',
|
||||
state: {
|
||||
unregisteredClientsDeletionStatus: {
|
||||
pending: [ 'abc123' ]
|
||||
pending: ['abc123']
|
||||
}
|
||||
},
|
||||
route: 'conflict-detection/conflicts',
|
||||
|
@ -435,26 +470,17 @@ describe('some action failure cases', () => {
|
|||
params: {
|
||||
nodesTested: {
|
||||
conflict: {
|
||||
'abc123': {}
|
||||
abc123: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
action: 'snoozeV3DeprecationWarning',
|
||||
state: {},
|
||||
route: 'v3deprecation',
|
||||
method: 'PUT',
|
||||
startAction: 'SNOOZE_V3DEPRECATION_WARNING_START',
|
||||
endAction: 'SNOOZE_V3DEPRECATION_WARNING_END',
|
||||
params: {}
|
||||
},
|
||||
{
|
||||
action: 'setConflictDetectionScanner',
|
||||
desc: 'when enabling',
|
||||
state: {},
|
||||
route: 'conflict-detection/until',
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
startAction: 'ENABLE_CONFLICT_DETECTION_SCANNER_START',
|
||||
endAction: 'ENABLE_CONFLICT_DETECTION_SCANNER_END',
|
||||
params: { enable: true }
|
||||
|
@ -464,7 +490,7 @@ describe('some action failure cases', () => {
|
|||
desc: 'when disabling',
|
||||
state: {},
|
||||
route: 'conflict-detection/until',
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
startAction: 'DISABLE_CONFLICT_DETECTION_SCANNER_START',
|
||||
endAction: 'DISABLE_CONFLICT_DETECTION_SCANNER_END',
|
||||
params: { enable: false }
|
||||
|
@ -482,7 +508,7 @@ describe('some action failure cases', () => {
|
|||
action: 'submitPendingOptions',
|
||||
state: STATE_TECH_CHANGE,
|
||||
route: 'config',
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
startAction: 'OPTIONS_FORM_SUBMIT_START',
|
||||
endAction: 'OPTIONS_FORM_SUBMIT_END',
|
||||
params: undefined
|
||||
|
@ -493,11 +519,11 @@ describe('some action failure cases', () => {
|
|||
|
||||
const data = {
|
||||
errors: {
|
||||
"code1": ["message1"],
|
||||
code1: ['message1']
|
||||
},
|
||||
error_data: {
|
||||
"code1": {
|
||||
"trace": 'some stack trace'
|
||||
code1: {
|
||||
trace: 'some stack trace'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -510,8 +536,8 @@ describe('some action failure cases', () => {
|
|||
resetAxiosMocks()
|
||||
})
|
||||
|
||||
cases.map(c => {
|
||||
describe(`${c.action}${ c.desc || ''}`, () => {
|
||||
cases.map((c) => {
|
||||
describe(`${c.action}${c.desc || ''}`, () => {
|
||||
let store = null
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -530,38 +556,44 @@ describe('some action failure cases', () => {
|
|||
response: {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
data: `${garbage}${JSON.stringify(data)}`,
|
||||
data: `${garbage}${JSON.stringify(data)}`
|
||||
// no confirmation header
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('reports warning and dispatches a failure action despite the garbage', done => {
|
||||
store.dispatch(actions[c.action](c.params)).then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
||||
error: expect.objectContaining({
|
||||
errors: expect.anything(),
|
||||
'error_data': expect.anything()
|
||||
}),
|
||||
confirmed: false,
|
||||
falsePositive: true,
|
||||
trimmed: garbage
|
||||
}))
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: c.startAction
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: c.endAction,
|
||||
success: false,
|
||||
message: expect.stringMatching(/[a-z]/)
|
||||
})
|
||||
]))
|
||||
done()
|
||||
})
|
||||
.catch(e => done(e))
|
||||
test('reports warning and dispatches a failure action despite the garbage', (done) => {
|
||||
store
|
||||
.dispatch(actions[c.action](c.params))
|
||||
.then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
error: expect.objectContaining({
|
||||
errors: expect.anything(),
|
||||
error_data: expect.anything()
|
||||
}),
|
||||
confirmed: false,
|
||||
falsePositive: true,
|
||||
trimmed: garbage
|
||||
})
|
||||
)
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: c.startAction
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: c.endAction,
|
||||
success: false,
|
||||
message: expect.stringMatching(/[a-z]/)
|
||||
})
|
||||
])
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch((e) => done(e))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -581,32 +613,38 @@ describe('some action failure cases', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('reports ui and console error messages', done => {
|
||||
test('reports ui and console error messages', (done) => {
|
||||
reportRequestError.mockReturnValueOnce(null)
|
||||
store.dispatch(actions[c.action](c.params)).then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
||||
error: expect.objectContaining({
|
||||
errors: expect.anything(),
|
||||
'error_data': expect.anything()
|
||||
}),
|
||||
confirmed: true,
|
||||
trimmed: ''
|
||||
}))
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: c.startAction
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: c.endAction,
|
||||
success: false,
|
||||
message: expect.stringMatching(/[a-z]/)
|
||||
})
|
||||
]))
|
||||
done()
|
||||
})
|
||||
.catch(e => done(e))
|
||||
store
|
||||
.dispatch(actions[c.action](c.params))
|
||||
.then(() => {
|
||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||
expect(reportRequestError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
error: expect.objectContaining({
|
||||
errors: expect.anything(),
|
||||
error_data: expect.anything()
|
||||
}),
|
||||
confirmed: true,
|
||||
trimmed: ''
|
||||
})
|
||||
)
|
||||
expect(store.getActions().length).toEqual(2)
|
||||
expect(store.getActions()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: c.startAction
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: c.endAction,
|
||||
success: false,
|
||||
message: expect.stringMatching(/[a-z]/)
|
||||
})
|
||||
])
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch((e) => done(e))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -653,10 +691,6 @@ describe('reportDetectedConflicts', () => {
|
|||
test.todo('success')
|
||||
})
|
||||
|
||||
describe('snoozeV3DeprecationWarning', () => {
|
||||
test.todo('success')
|
||||
})
|
||||
|
||||
describe('setConflictDetectionScanner', () => {
|
||||
test.todo('success when enabling')
|
||||
test.todo('success when disabling')
|
||||
|
@ -678,7 +712,7 @@ describe('preprocessResponse', () => {
|
|||
|
||||
actions.preprocessResponse(response)
|
||||
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({confirmed: true}))
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({ confirmed: true }))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -687,7 +721,7 @@ describe('preprocessResponse', () => {
|
|||
const method = 'PUT'
|
||||
const status = 405
|
||||
const statusText = 'Method Not Allowed'
|
||||
const requestData = JSON.stringify({bar: 43})
|
||||
const requestData = JSON.stringify({ bar: 43 })
|
||||
const requestHeaders = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
@ -739,17 +773,19 @@ describe('preprocessResponse', () => {
|
|||
|
||||
actions.preprocessResponse(response)
|
||||
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
||||
confirmed: false,
|
||||
requestData,
|
||||
requestMethod: method,
|
||||
requestUrl: url,
|
||||
responseStatus: status,
|
||||
responseStatusText: statusText,
|
||||
requestData: REDACTED_REQUEST_DATA,
|
||||
responseHeaders: REDACTED_HEADERS,
|
||||
requestHeaders: REDACTED_HEADERS
|
||||
}))
|
||||
expect(reportRequestError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
confirmed: false,
|
||||
requestData,
|
||||
requestMethod: method,
|
||||
requestUrl: url,
|
||||
responseStatus: status,
|
||||
responseStatusText: statusText,
|
||||
requestData: REDACTED_REQUEST_DATA,
|
||||
responseHeaders: REDACTED_HEADERS,
|
||||
requestHeaders: REDACTED_HEADERS
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -780,17 +816,19 @@ describe('preprocessResponse', () => {
|
|||
|
||||
actions.preprocessResponse(response)
|
||||
|
||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
||||
confirmed: false,
|
||||
requestMethod: method,
|
||||
requestUrl: url,
|
||||
responseStatus: status,
|
||||
responseStatusText: statusText,
|
||||
responseData,
|
||||
requestData: REDACTED_REQUEST_DATA,
|
||||
responseHeaders: REDACTED_HEADERS,
|
||||
requestHeaders: REDACTED_HEADERS
|
||||
}))
|
||||
expect(reportRequestError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
confirmed: false,
|
||||
requestMethod: method,
|
||||
requestUrl: url,
|
||||
responseStatus: status,
|
||||
responseStatusText: statusText,
|
||||
responseData,
|
||||
requestData: REDACTED_REQUEST_DATA,
|
||||
responseHeaders: REDACTED_HEADERS,
|
||||
requestHeaders: REDACTED_HEADERS
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,22 +1,13 @@
|
|||
import { createStore as reduxCreateStore, applyMiddleware, compose } from 'redux'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import { thunk } from 'redux-thunk'
|
||||
import rootReducer from './reducers'
|
||||
|
||||
const middleware = [ thunkMiddleware ]
|
||||
const middleware = [thunk]
|
||||
|
||||
const composeEnhancers = (
|
||||
process.env.NODE_ENV === 'development'
|
||||
&& window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||
) || compose
|
||||
const composeEnhancers = (process.env.NODE_ENV === 'development' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
|
||||
|
||||
const enhancer = composeEnhancers(
|
||||
applyMiddleware(...middleware)
|
||||
)
|
||||
const enhancer = composeEnhancers(applyMiddleware(...middleware))
|
||||
|
||||
export function createStore(initialData = {}) {
|
||||
return reduxCreateStore(
|
||||
rootReducer,
|
||||
initialData,
|
||||
enhancer
|
||||
)
|
||||
return reduxCreateStore(rootReducer, initialData, enhancer)
|
||||
}
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import size from 'lodash/size'
|
||||
import omit from 'lodash/omit'
|
||||
import get from 'lodash/get'
|
||||
import { size, omit, get } from 'lodash'
|
||||
import { combineReducers } from 'redux'
|
||||
|
||||
export const ADMIN_TAB_SETTINGS = 'ADMIN_TAB_SETTINGS'
|
||||
export const ADMIN_TAB_TROUBLESHOOT = 'ADMIN_TAB_TROUBLESHOOT'
|
||||
|
||||
const coerceBool = val => val === true || val === "1"
|
||||
const coerceBool = (val) => val === true || val === '1'
|
||||
|
||||
const coerceEmptyArrayToEmptyObject = val => size(val) === 0 ? {} : val
|
||||
const coerceEmptyArrayToEmptyObject = (val) => (size(val) === 0 ? {} : val)
|
||||
|
||||
// TODO: add reducer for the clientPreferences that coerces their boolean options
|
||||
|
||||
export function blocklistSelector(state = {}) {
|
||||
const unregisteredClients = state.unregisteredClients || {}
|
||||
|
||||
return Object.keys( unregisteredClients ).reduce( (acc, md5) => {
|
||||
if( get( unregisteredClients, [md5, 'blocked'], false ) ) {
|
||||
return Object.keys(unregisteredClients).reduce((acc, md5) => {
|
||||
if (get(unregisteredClients, [md5, 'blocked'], false)) {
|
||||
acc.push(md5)
|
||||
}
|
||||
return acc
|
||||
|
@ -26,21 +24,13 @@ export function blocklistSelector(state = {}) {
|
|||
export function options(state = {}, action = {}) {
|
||||
const { type, data } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'OPTIONS_FORM_SUBMIT_END':
|
||||
if(! get(action, 'data.options')) {
|
||||
if (!get(action, 'data.options')) {
|
||||
return state
|
||||
} else {
|
||||
const {
|
||||
options: {
|
||||
technology,
|
||||
usePro,
|
||||
compat,
|
||||
pseudoElements,
|
||||
version,
|
||||
kitToken,
|
||||
apiToken
|
||||
}
|
||||
options: { technology, usePro, compat, pseudoElements, version, kitToken, apiToken }
|
||||
} = data
|
||||
|
||||
return {
|
||||
|
@ -65,11 +55,10 @@ const OPTIONS_FORM_INITIAL_STATE = {
|
|||
message: ''
|
||||
}
|
||||
|
||||
function optionsFormState(
|
||||
state = OPTIONS_FORM_INITIAL_STATE, action = {}) {
|
||||
function optionsFormState(state = OPTIONS_FORM_INITIAL_STATE, action = {}) {
|
||||
const { type, success, message } = action
|
||||
|
||||
switch(type) {
|
||||
|
||||
switch (type) {
|
||||
case 'OPTIONS_FORM_SUBMIT_START':
|
||||
return { ...state, isSubmitting: true }
|
||||
case 'OPTIONS_FORM_SUBMIT_END':
|
||||
|
@ -93,10 +82,10 @@ const INITIAL_STATE_BLOCKLIST_UPDATE_STATUS = {
|
|||
message: ''
|
||||
}
|
||||
|
||||
function blocklistUpdateStatus( state = INITIAL_STATE_BLOCKLIST_UPDATE_STATUS, action = {} ) {
|
||||
function blocklistUpdateStatus(state = INITIAL_STATE_BLOCKLIST_UPDATE_STATUS, action = {}) {
|
||||
const { type, success, message } = action
|
||||
|
||||
switch(type) {
|
||||
|
||||
switch (type) {
|
||||
case 'BLOCKLIST_UPDATE_RESET':
|
||||
return INITIAL_STATE_BLOCKLIST_UPDATE_STATUS
|
||||
case 'BLOCKLIST_UPDATE_START':
|
||||
|
@ -104,7 +93,7 @@ function blocklistUpdateStatus( state = INITIAL_STATE_BLOCKLIST_UPDATE_STATUS, a
|
|||
case 'BLOCKLIST_UPDATE_END':
|
||||
return { ...state, isSubmitting: false, pending: null, hasSubmitted: true, success, message }
|
||||
case 'UPDATE_PENDING_BLOCKLIST':
|
||||
if(Array.isArray(action.data) || null === action.data) {
|
||||
if (Array.isArray(action.data) || null === action.data) {
|
||||
return {
|
||||
...state,
|
||||
hasSubmitted: false,
|
||||
|
@ -128,12 +117,10 @@ const INITIAL_STATE_UNREGISTERED_CLIENTS_DELETION_STATUS = {
|
|||
message: ''
|
||||
}
|
||||
|
||||
function unregisteredClientsDeletionStatus(
|
||||
state = INITIAL_STATE_UNREGISTERED_CLIENTS_DELETION_STATUS,
|
||||
action = {} ) {
|
||||
function unregisteredClientsDeletionStatus(state = INITIAL_STATE_UNREGISTERED_CLIENTS_DELETION_STATUS, action = {}) {
|
||||
const { type, success, message } = action
|
||||
|
||||
switch(type) {
|
||||
|
||||
switch (type) {
|
||||
case 'DELETE_UNREGISTERED_CLIENTS_RESET':
|
||||
return INITIAL_STATE_UNREGISTERED_CLIENTS_DELETION_STATUS
|
||||
case 'DELETE_UNREGISTERED_CLIENTS_START':
|
||||
|
@ -141,7 +128,7 @@ function unregisteredClientsDeletionStatus(
|
|||
case 'DELETE_UNREGISTERED_CLIENTS_END':
|
||||
return { ...state, isSubmitting: false, pending: [], hasSubmitted: true, success, message }
|
||||
case 'UPDATE_PENDING_UNREGISTERED_CLIENTS_FOR_DELETION':
|
||||
if( Array.isArray(action.data) ) {
|
||||
if (Array.isArray(action.data)) {
|
||||
return { ...state, hasSubmitted: false, pending: action.data, success: false, message: '' }
|
||||
} else {
|
||||
return state
|
||||
|
@ -154,9 +141,9 @@ function unregisteredClientsDeletionStatus(
|
|||
function pendingOptions(state = {}, action = {}) {
|
||||
const { type, change, activeKitToken, concreteVersion } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'ADD_PENDING_OPTION':
|
||||
return {...state, ...change}
|
||||
return { ...state, ...change }
|
||||
case 'RESET_PENDING_OPTION':
|
||||
const option = Object.keys(change)[0]
|
||||
return omit(state, option)
|
||||
|
@ -173,16 +160,16 @@ function pendingOptions(state = {}, action = {}) {
|
|||
|
||||
function preferenceConflicts(state = {}, action = {}) {
|
||||
const { type } = action
|
||||
|
||||
switch(type) {
|
||||
|
||||
switch (type) {
|
||||
case 'OPTIONS_FORM_SUBMIT_END':
|
||||
if ( ! action.success ) {
|
||||
if (!action.success) {
|
||||
return state
|
||||
}
|
||||
|
||||
const conflicts = get(action, 'data.conflicts')
|
||||
|
||||
if(!! conflicts) {
|
||||
if (!!conflicts) {
|
||||
return coerceEmptyArrayToEmptyObject(conflicts)
|
||||
} else {
|
||||
return coerceEmptyArrayToEmptyObject(state)
|
||||
|
@ -200,16 +187,16 @@ function preferenceConflictDetection(
|
|||
message: ''
|
||||
},
|
||||
action = {}
|
||||
){
|
||||
) {
|
||||
const { type, success, message } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'PREFERENCE_CHECK_START':
|
||||
return { ...state, isChecking: true }
|
||||
case 'PREFERENCE_CHECK_END':
|
||||
return { ...state, isChecking: false, hasChecked: true, success, message }
|
||||
case 'OPTIONS_FORM_SUBMIT_END':
|
||||
return { ...state, isChecking: false, hasChecked: false, success: false, message: ''}
|
||||
return { ...state, isChecking: false, hasChecked: false, success: false, message: '' }
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
@ -222,10 +209,11 @@ function kitsQueryStatus(
|
|||
isSubmitting: false,
|
||||
message: ''
|
||||
},
|
||||
action = {}) {
|
||||
action = {}
|
||||
) {
|
||||
const { type, success, message } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'KITS_QUERY_START':
|
||||
return { ...state, isSubmitting: true }
|
||||
case 'KITS_QUERY_END':
|
||||
|
@ -235,11 +223,11 @@ function kitsQueryStatus(
|
|||
}
|
||||
}
|
||||
|
||||
function kits( state = [], action = {} ) {
|
||||
function kits(state = [], action = {}) {
|
||||
const { type, data, success } = action
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'KITS_QUERY_END':
|
||||
if(success) {
|
||||
if (success) {
|
||||
return get(data, 'me.kits', [])
|
||||
} else {
|
||||
return state
|
||||
|
@ -252,7 +240,7 @@ function kits( state = [], action = {} ) {
|
|||
function pendingOptionConflicts(state = {}, action = {}) {
|
||||
const { type, detectedConflicts = {} } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'PREFERENCE_CHECK_END':
|
||||
return { ...detectedConflicts }
|
||||
case 'OPTIONS_FORM_SUBMIT_END':
|
||||
|
@ -264,36 +252,36 @@ function pendingOptionConflicts(state = {}, action = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
function detectConflictsUntil( state = 0, action = {} ) {
|
||||
function detectConflictsUntil(state = 0, action = {}) {
|
||||
const { type, data } = action
|
||||
const intValue = parseInt( get(data, 'detectConflictsUntil') )
|
||||
const intValue = parseInt(get(data, 'detectConflictsUntil'))
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'ENABLE_CONFLICT_DETECTION_SCANNER_END':
|
||||
case 'DISABLE_CONFLICT_DETECTION_SCANNER_END':
|
||||
if(action.success && null !== data ) {
|
||||
if (action.success && null !== data) {
|
||||
return isNaN(intValue) ? 0 : intValue
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
default:
|
||||
const initialIntValue = parseInt( state )
|
||||
const initialIntValue = parseInt(state)
|
||||
return isNaN(initialIntValue) ? 0 : initialIntValue
|
||||
}
|
||||
}
|
||||
|
||||
function unregisteredClients( state = {}, action = {} ) {
|
||||
function unregisteredClients(state = {}, action = {}) {
|
||||
const { type, data } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'CONFLICT_DETECTION_SUBMIT_END':
|
||||
if( action.success && null !== data ) {
|
||||
if (action.success && null !== data) {
|
||||
return coerceEmptyArrayToEmptyObject(data)
|
||||
} else {
|
||||
return coerceEmptyArrayToEmptyObject(state)
|
||||
}
|
||||
case 'BLOCKLIST_UPDATE_END':
|
||||
if(action.success && Array.isArray(data)) {
|
||||
if (action.success && Array.isArray(data)) {
|
||||
const updatedState = Object.keys(state).reduce(
|
||||
(acc, md5) => {
|
||||
acc[md5].blocked = !!~data.indexOf(md5)
|
||||
|
@ -306,7 +294,7 @@ function unregisteredClients( state = {}, action = {} ) {
|
|||
return coerceEmptyArrayToEmptyObject(state)
|
||||
}
|
||||
case 'DELETE_UNREGISTERED_CLIENTS_END':
|
||||
if(action.success && !!data) {
|
||||
if (action.success && !!data) {
|
||||
return data
|
||||
} else {
|
||||
return coerceEmptyArrayToEmptyObject(state)
|
||||
|
@ -327,11 +315,11 @@ function unregisteredClientDetectionStatus(
|
|||
recentConflictsDetected: {},
|
||||
message: ''
|
||||
},
|
||||
action = {}) {
|
||||
|
||||
action = {}
|
||||
) {
|
||||
const { type, success, message, unregisteredClientsBeforeDetection, recentConflictsDetected } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'CONFLICT_DETECTION_SUBMIT_START':
|
||||
return { ...state, isSubmitting: true, unregisteredClientsBeforeDetection, recentConflictsDetected }
|
||||
case 'CONFLICT_DETECTION_SUBMIT_END':
|
||||
|
@ -358,11 +346,11 @@ function conflictDetectionScannerStatus(
|
|||
success: false,
|
||||
message: ''
|
||||
},
|
||||
action = {}) {
|
||||
|
||||
action = {}
|
||||
) {
|
||||
const { type, success, message } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'ENABLE_CONFLICT_DETECTION_SCANNER_START':
|
||||
case 'DISABLE_CONFLICT_DETECTION_SCANNER_START':
|
||||
return { ...state, hasSubmitted: false, success: false, isSubmitting: true }
|
||||
|
@ -374,48 +362,17 @@ function conflictDetectionScannerStatus(
|
|||
}
|
||||
}
|
||||
|
||||
function v3DeprecationWarningStatus(
|
||||
state = {
|
||||
isSubmitting: false,
|
||||
hasSubmitted: false,
|
||||
success: false,
|
||||
message: ''
|
||||
},
|
||||
action = {}) {
|
||||
const { type, success, message } = action
|
||||
|
||||
switch(type) {
|
||||
case 'SNOOZE_V3DEPRECATION_WARNING_START':
|
||||
return { ...state, isSubmitting: true, hasSubmitted: true }
|
||||
case 'SNOOZE_V3DEPRECATION_WARNING_END':
|
||||
return { ...state, isSubmitting: false, success, message }
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function v3DeprecationWarning(state = {}, action = {}) {
|
||||
const { type, snooze = false } = action
|
||||
|
||||
switch(type) {
|
||||
case 'SNOOZE_V3DEPRECATION_WARNING_END':
|
||||
return { ...state, snooze }
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function showConflictDetectionReporter(state = false, action = {}) {
|
||||
const { type } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'ENABLE_CONFLICT_DETECTION_SCANNER_END':
|
||||
return action.success
|
||||
case 'DISABLE_CONFLICT_DETECTION_SCANNER_END':
|
||||
// If we failed trying to disable the scanner, then it should remain
|
||||
// visible to present the error state. If we succeeded, then we could
|
||||
// stop showing it.
|
||||
return ! action.success
|
||||
return !action.success
|
||||
case 'CONFLICT_DETECTION_TIMER_EXPIRED':
|
||||
return false
|
||||
default:
|
||||
|
@ -426,7 +383,7 @@ function showConflictDetectionReporter(state = false, action = {}) {
|
|||
function userAttemptedToStopScanner(state = false, action = {}) {
|
||||
const { type } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'USER_STOP_SCANNER':
|
||||
return true
|
||||
case 'ENABLE_CONFLICT_DETECTION_SCANNER_START':
|
||||
|
@ -440,7 +397,7 @@ function userAttemptedToStopScanner(state = false, action = {}) {
|
|||
function activeAdminTab(state = ADMIN_TAB_SETTINGS, action = {}) {
|
||||
const { type, tab } = action
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'SET_ACTIVE_ADMIN_TAB':
|
||||
return tab
|
||||
default:
|
||||
|
@ -448,12 +405,15 @@ function activeAdminTab(state = ADMIN_TAB_SETTINGS, action = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
function simple(state = {}, _action) { return state }
|
||||
function simple(state = {}, _action) {
|
||||
return state
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
activeAdminTab,
|
||||
apiNonce: simple,
|
||||
apiUrl: simple,
|
||||
faApiUrl: simple,
|
||||
blocklistUpdateStatus,
|
||||
clientPreferences: coerceEmptyArrayToEmptyObject,
|
||||
conflictDetectionScannerStatus,
|
||||
|
@ -472,18 +432,13 @@ export default combineReducers({
|
|||
rootUrl: simple,
|
||||
mainCdnAssetUrl: simple,
|
||||
mainCdnAssetIntegrity: simple,
|
||||
enableIconChooser: coerceBool,
|
||||
releases: simple,
|
||||
settingsPageUrl: simple,
|
||||
showAdmin: coerceBool,
|
||||
showConflictDetectionReporter,
|
||||
unregisteredClientDetectionStatus,
|
||||
unregisteredClients,
|
||||
unregisteredClientsDeletionStatus,
|
||||
unregisteredClientsDeletionStatus,
|
||||
userAttemptedToStopScanner,
|
||||
v3DeprecationWarning,
|
||||
v3DeprecationWarningStatus,
|
||||
webpackPublicPath: simple,
|
||||
isGutenbergPage: coerceBool,
|
||||
usingCompatJs: coerceBool
|
||||
webpackPublicPath: simple
|
||||
})
|
||||
|
|
|
@ -9,7 +9,7 @@ describe('options', () => {
|
|||
|
||||
describe('no action match', () => {
|
||||
test('return unchanged state', () => {
|
||||
expect(options({ foo: 42 })).toEqual({foo: 42})
|
||||
expect(options({ foo: 42 })).toEqual({ foo: 42 })
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -21,7 +21,7 @@ describe('options', () => {
|
|||
usePro: true,
|
||||
compat: false,
|
||||
pseudoElements: true,
|
||||
version: '5.11.2',
|
||||
version: '5.11.2'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ describe('options', () => {
|
|||
type: 'OPTIONS_FORM_SUBMIT_END'
|
||||
}
|
||||
|
||||
expect(options({ foo: 42 }, action)).toEqual({foo: 42})
|
||||
expect(options({ foo: 42 }, action)).toEqual({ foo: 42 })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
export async function resetOptions(page) {
|
||||
return page.evaluate(() => {
|
||||
const { apiUrl, apiNonce} = window.__FontAwesomeOfficialPlugin__
|
||||
const { apiUrl, apiNonce } = window.__FontAwesomeOfficialPlugin__
|
||||
|
||||
let DEFAULT_OPTIONS = {
|
||||
options:{
|
||||
usePro:false,
|
||||
compat:true,
|
||||
technology:"webfont",
|
||||
pseudoElements:true,
|
||||
kitToken:null,
|
||||
apiToken:true,
|
||||
version:"6.0.0-beta3"
|
||||
options: {
|
||||
usePro: false,
|
||||
compat: true,
|
||||
technology: 'webfont',
|
||||
pseudoElements: true,
|
||||
kitToken: null,
|
||||
apiToken: true,
|
||||
version: '6.0.0-beta3'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export const MOCK_UI_MESSAGE = 'mock ui message'
|
||||
const DEFAULT_REPORT_IMPL = () => MOCK_UI_MESSAGE
|
||||
const mockDefaultFn = jest.fn( DEFAULT_REPORT_IMPL )
|
||||
const mockDefaultFn = jest.fn(DEFAULT_REPORT_IMPL)
|
||||
|
||||
export const redactRequestData = jest.fn()
|
||||
export const redactHeaders = jest.fn()
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue