Compare commits
38 Commits
5.0.0-alph
...
main
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 |
|
@ -2,13 +2,13 @@ name: Jest
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches:
|
||||||
|
- "**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
jest:
|
jest:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -16,12 +16,12 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: "18"
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Cache node_module
|
- name: Cache node_module
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: admin/node_modules
|
path: admin/node_modules
|
||||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('admin/package-lock.json') }}
|
key: ${{ runner.os }}-node-modules-${{ hashFiles('admin/package-lock.json') }}
|
||||||
|
|
|
@ -2,7 +2,8 @@ name: PHP Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches:
|
||||||
|
- "**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
|
|
||||||
|
@ -18,7 +19,6 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
@ -34,12 +34,10 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
|
php: ["7.4", "8.0", "8.1", "8.2", "8.3"]
|
||||||
wordpress: [latest]
|
wordpress: [latest]
|
||||||
include:
|
include:
|
||||||
- php: '5.6'
|
- php: "8.3"
|
||||||
wordpress: 5.2.5
|
|
||||||
- php: '8.3'
|
|
||||||
wordpress: trunk
|
wordpress: trunk
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -48,6 +46,7 @@ jobs:
|
||||||
- uses: shivammathur/setup-php@v2
|
- uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: uopz
|
||||||
|
|
||||||
- name: Validate composer.json and composer.lock
|
- name: Validate composer.json and composer.lock
|
||||||
id: composer-lock
|
id: composer-lock
|
||||||
|
@ -225,6 +224,8 @@ jobs:
|
||||||
vendor/bin/phpunit $PHP_UNIT_ARGS --group slow
|
vendor/bin/phpunit $PHP_UNIT_ARGS --group slow
|
||||||
|
|
||||||
- name: Run PHPUnit Loader Tests
|
- 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: |
|
run: |
|
||||||
WP_PLUGIN_DIR="$(pwd)" \
|
WP_PLUGIN_DIR="$(pwd)" \
|
||||||
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
COMPOSER_VENDOR_DIR="$WP_PLUGIN_DIR/vendor" \
|
||||||
|
@ -267,7 +268,7 @@ jobs:
|
||||||
|
|
||||||
- name: Maybe run phpcs
|
- name: Maybe run phpcs
|
||||||
run: |
|
run: |
|
||||||
if [ ${{ matrix.php }} == '8.3' ] && [ ${{ matrix.wordpress }} == latest ]; then
|
if [ ${{ matrix.php }} == '8.2' ] && [ ${{ matrix.wordpress }} == latest ]; then
|
||||||
composer phpcs
|
composer phpcs
|
||||||
echo
|
echo
|
||||||
echo "Skipping phpcs"
|
echo "Skipping phpcs"
|
||||||
|
|
|
@ -20,3 +20,7 @@ webpack-stats.json
|
||||||
admin/src/playwright/.auth/
|
admin/src/playwright/.auth/
|
||||||
admin/artifacts/
|
admin/artifacts/
|
||||||
admin/test-results/
|
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"/>
|
<arg name="extensions" value="php"/>
|
||||||
<file>includes</file>
|
<file>includes</file>
|
||||||
<file>tests</file>
|
<file>tests</file>
|
||||||
|
<!-- TODO: maybe re-enable this. It's crashing phpcbf. -->
|
||||||
|
<!--
|
||||||
<file>font-awesome.php</file>
|
<file>font-awesome.php</file>
|
||||||
|
-->
|
||||||
<file>font-awesome-init.php</file>
|
<file>font-awesome-init.php</file>
|
||||||
|
<file>block-editor/font-awesome-icon-block-init.php</file>
|
||||||
<file>index.php</file>
|
<file>index.php</file>
|
||||||
<file>v3shims.php</file>
|
|
||||||
<file>defines.php</file>
|
<file>defines.php</file>
|
||||||
<file>admin/index.php</file>
|
<file>admin/index.php</file>
|
||||||
<file>admin/views/main.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/
|
103
DEVELOPMENT.md
|
@ -41,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
|
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
|
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
|
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".
|
from the WordPress plugin directory by searching for plugins by author "fontawesome".
|
||||||
|
|
||||||
|
@ -456,27 +456,6 @@ brew install composer
|
||||||
```
|
```
|
||||||
</details>
|
</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>
|
<details>
|
||||||
<summary>If you have an older version of Docker or one that doesn't support host.docker.internal</summary>
|
<summary>If you have an older version of Docker or one that doesn't support host.docker.internal</summary>
|
||||||
|
|
||||||
|
@ -820,7 +799,7 @@ When `mod_security` is enabled, it'll look like this:
|
||||||
|
|
||||||
3. Update the plugin version const in `includes/class-fontawesome.php`
|
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.
|
5. Wait on changing the "Stable Tag" in `readme.txt` until after we've made the changes in the `svn` repo below.
|
||||||
|
|
||||||
|
@ -845,7 +824,7 @@ When `mod_security` is enabled, it'll look like this:
|
||||||
|
|
||||||
- `git add docs` to stage them for commit (and eventually commit them)
|
- `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
|
```bash
|
||||||
bin/composer dist
|
bin/composer dist
|
||||||
|
@ -856,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
|
This will cause everything to be built inside the container, which will hopefully
|
||||||
keep the built assets more consistent, regardless of the host environment.)
|
keep the built assets more consistent, regardless of the host environment.)
|
||||||
|
|
||||||
This will delete the previous build assets and produce the following:
|
This will delete the previous build assets and produce:
|
||||||
|
|
||||||
`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:
|
|
||||||
|
|
||||||
`wp-dist/`: the contents of this directory contains everything that will be used in
|
`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
|
subsequent steps to both build an installable zip file, and to copy into the
|
||||||
|
@ -884,13 +850,7 @@ a GitHub release.
|
||||||
|
|
||||||
9. Run through some manual acceptance testing
|
9. Run through some manual acceptance testing
|
||||||
|
|
||||||
**WordPress 4.7, 4.8, 4.9**
|
**WordPress 6.0**
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
Install and activate the Font Awesome plugin from the admin dashboard by uploading the `font-awesome.zip` file
|
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.
|
that was created in the previous step.
|
||||||
|
@ -899,31 +859,16 @@ Run through the following, with the JavaScript console open, looking for any war
|
||||||
|
|
||||||
1. Load the plugin settings page.
|
1. Load the plugin settings page.
|
||||||
1. Change from Web Font to SVG and save.
|
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. Click the "Add Font Awesome" button
|
||||||
1. Search for something, and click to insert an icon from the results
|
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. Create a new post, switching to the Gutenberg / Block Editor
|
||||||
1. Expect to see a compatibility warning that the Icon Chooser is not enabled,
|
1. Add an icon block to a post
|
||||||
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. Search for something, and click to insert an icon from the results
|
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**
|
**WordPress latest**
|
||||||
|
|
||||||
|
@ -1142,7 +1087,7 @@ $ cd ..
|
||||||
10. Copy plugin directory assets and wp-dist layout into `wp-svn/trunk`
|
10. Copy plugin directory assets and wp-dist layout into `wp-svn/trunk`
|
||||||
|
|
||||||
```bash
|
```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
|
This script will just `rm *` anything under `wp-svn/trunk/*` and `wp-svn/assets/*` to make sure that if the new dist
|
||||||
|
@ -1200,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).
|
[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
|
13. Create the new svn release tag
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
First, make sure `svn stat` is clean. We want to make sure that the trunk is all committed and clean before we take a
|
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.
|
snapshot of it for the release tag.
|
||||||
|
@ -1219,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
|
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`
|
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:
|
We've now got three copies of `readme.txt` that should all be updated with new tag values:
|
||||||
|
@ -1347,7 +1296,7 @@ wp --allow-root core update --version=5.4 /tmp/wordpress-5.4-latest.zip
|
||||||
|
|
||||||
# Analyze Webpack Bundle
|
# 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
|
which produces a corresponding `webpack-stats.html` file in the corresponding
|
||||||
directory on each build.
|
directory on each build.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"htmlWhitespaceSensitivity": "css",
|
||||||
|
"printWidth": 160,
|
||||||
|
"quoteProps": "consistent",
|
||||||
|
"semi": false,
|
||||||
|
"singleAttributePerLine": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"useTabs": false
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
"use strict";(self.webpackChunkfont_awesome_admin=self.webpackChunkfont_awesome_admin||[]).push([[250],{1250:(e,o,s)=>{s.r(o),s.d(o,{default:()=>r});var t=s(1083);const r=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 @@
|
||||||
"use strict";(self.webpackChunkfont_awesome_admin=self.webpackChunkfont_awesome_admin||[]).push([[268],{9268:(e,a,r)=>{r.r(a),r.d(a,{default:()=>n});var t=r(634),o=r.n(t);const n=e=>async(a,r)=>{try{const{apiNonce:t,rootUrl:n,restApiNamespace:s}=e;return o().use(o().createRootURLMiddleware(n)),o().use(o().createNonceMiddleware(t)),await o()({path:`${s}/api`,method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:a.replace(/\s+/g," "),variables:r})})}catch(e){throw console.error("CAUGHT:",e),new Error(e)}}}}]);
|
|
|
@ -1 +0,0 @@
|
||||||
"use strict";(self.webpackChunkfont_awesome_admin=self.webpackChunkfont_awesome_admin||[]).push([[331],{3331:(e,t,s)=>{s.r(t),s.d(t,{scopeCss:()=>T});const r="-shadowcsshost",c="-shadowcssslotted",o="-shadowcsscontext",n=")(?:\\(((?:\\([^)(]*\\)|[^)(]*)+?)\\))?([^,{]*)",l=new RegExp("("+r+n,"gim"),a=new RegExp("("+o+n,"gim"),i=new RegExp("("+c+n,"gim"),p=r+"-no-combinator",h=/-shadowcsshost-no-combinator([^\s]*)/,u=[/::shadow/g,/::content/g],g=/-shadowcsshost/gim,d=/:host/gim,m=/::slotted/gim,f=/:host-context/gim,_=/\/\*\s*[\s\S]*?\*\//g,$=/\/\*\s*#\s*source(Mapping)?URL=[\s\S]+?\*\//g,x=/(\s*)([^;\{\}]+?)(\s*)((?:{%BLOCK%}?\s*;?)|(?:\s*;))/g,w=/([{}])/g,b=/(^.*?[^\\])??((:+)(.*)|$)/,S="%BLOCK%",W=(e,t)=>{const s=k(e);let r=0;return s.escapedString.replace(x,((...e)=>{const c=e[2];let o="",n=e[4],l="";n&&n.startsWith("{"+S)&&(o=s.blocks[r++],n=n.substring(8),l="{");const a=t({selector:c,content:o});return`${e[1]}${a.selector}${e[3]}${l}${a.content}${n}`}))},k=e=>{const t=e.split(w),s=[],r=[];let c=0,o=[];for(let e=0;e<t.length;e++){const n=t[e];"}"===n&&c--,c>0?o.push(n):(o.length>0&&(r.push(o.join("")),s.push(S),o=[]),s.push(n)),"{"===n&&c++}return o.length>0&&(r.push(o.join("")),s.push(S)),{escapedString:s.join(""),blocks:r}},O=(e,t,s)=>e.replace(t,((...e)=>{if(e[2]){const t=e[2].split(","),r=[];for(let c=0;c<t.length;c++){const o=t[c].trim();if(!o)break;r.push(s(p,o,e[3]))}return r.join(",")}return p+e[3]})),j=(e,t,s)=>e+t.replace(r,"")+s,E=(e,t,s)=>t.indexOf(r)>-1?j(e,t,s):e+t+s+", "+t+" "+e+s,R=(e,t)=>e.replace(b,((e,s="",r,c="",o="")=>s+t+c+o)),C=(e,t,s,r,c)=>W(e,(e=>{let c=e.selector,o=e.content;return"@"!==e.selector[0]?c=((e,t,s,r)=>e.split(",").map((e=>r&&e.indexOf("."+r)>-1?e.trim():((e,t)=>!(e=>(e=e.replace(/\[/g,"\\[").replace(/\]/g,"\\]"),new RegExp("^("+e+")([>\\s~+[.,{:][\\s\\S]*)?$","m")))(t).test(e))(e,t)?((e,t,s)=>{const r="."+(t=t.replace(/\[is=([^\]]*)\]/g,((e,...t)=>t[0]))),c=e=>{let c=e.trim();if(!c)return"";if(e.indexOf(p)>-1)c=((e,t,s)=>{if(g.lastIndex=0,g.test(e)){const t=`.${s}`;return e.replace(h,((e,s)=>R(s,t))).replace(g,t+" ")}return t+" "+e})(e,t,s);else{const t=e.replace(g,"");t.length>0&&(c=R(t,r))}return c},o=(e=>{const t=[];let s,r=0;return s=(e=e.replace(/(\[[^\]]*\])/g,((e,s)=>{const c=`__ph-${r}__`;return t.push(s),r++,c}))).replace(/(:nth-[-\w]+)(\([^)]+\))/g,((e,s,c)=>{const o=`__ph-${r}__`;return t.push(c),r++,s+o})),{content:s,placeholders:t}})(e);let n,l="",a=0;const i=/( |>|\+|~(?!=))\s*/g;let u=!((e=o.content).indexOf(p)>-1);for(;null!==(n=i.exec(e));){const t=n[1],s=e.slice(a,n.index).trim();u=u||s.indexOf(p)>-1,l+=`${u?c(s):s} ${t} `,a=i.lastIndex}const d=e.substring(a);return u=u||d.indexOf(p)>-1,l+=u?c(d):d,m=o.placeholders,l.replace(/__ph-(\d+)__/g,((e,t)=>m[+t]));var m})(e,t,s).trim():e.trim())).join(", "))(e.selector,t,s,r):(e.selector.startsWith("@media")||e.selector.startsWith("@supports")||e.selector.startsWith("@page")||e.selector.startsWith("@document"))&&(o=C(e.content,t,s,r)),{selector:c.replace(/\s{2,}/g," ").trim(),content:o}})),T=(e,t,s)=>{const n=t+"-h",h=t+"-s",g=e.match($)||[];e=e.replace(_,"");const x=[];if(s){const t=e=>{const t=`/*!@___${x.length}___*/`,s=`/*!@${e.selector}*/`;return x.push({placeholder:t,comment:s}),e.selector=t+e.selector,e};e=W(e,(e=>"@"!==e.selector[0]?t(e):e.selector.startsWith("@media")||e.selector.startsWith("@supports")||e.selector.startsWith("@page")||e.selector.startsWith("@document")?(e.content=W(e.content,t),e):e))}const w=((e,t,s,n,h)=>{const g=((e,t)=>{const s="."+t+" > ",r=[];return e=e.replace(i,((...e)=>{if(e[2]){const t=e[2].trim(),c=e[3],o=s+t+c;let n="";for(let t=e[4]-1;t>=0;t--){const s=e[5][t];if("}"===s||","===s)break;n=s+n}const l=n+o,a=`${n.trimRight()}${o.trim()}`;if(l.trim()!==a.trim()){const e=`${a}, ${l}`;r.push({orgSelector:l,updatedSelector:e})}return o}return p+e[3]})),{selectors:r,cssText:e}})(e=(e=>O(e,a,E))(e=(e=>O(e,l,j))(e=e.replace(f,o).replace(d,r).replace(m,c))),n);return e=(e=>u.reduce(((e,t)=>e.replace(t," ")),e))(e=g.cssText),t&&(e=C(e,t,s,n)),{cssText:(e=(e=e.replace(/-shadowcsshost-no-combinator/g,`.${s}`)).replace(/>\s*\*\s+([^{, ]+)/gm," $1 ")).trim(),slottedSelectors:g.selectors}})(e,t,n,h);return e=[w.cssText,...g].join("\n"),s&&x.forEach((({placeholder:t,comment:s})=>{e=e.replace(t,s)})),w.slottedSelectors.forEach((t=>{e=e.replace(t.orgSelector,t.updatedSelector)})),e}}}]);
|
|
|
@ -8,4 +8,3 @@
|
||||||
|
|
||||||
.FGrSfvJewATz8TfOqA_j th.dDmxKRAWr1lhLPK3Z838,td.dDmxKRAWr1lhLPK3Z838{background-color:#ffe2e2}
|
.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}
|
.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}
|
||||||
.n4FnenNNXVSnogTxvEag{background-color:#fdfdf3;border:1px solid #000;display:inline-block;padding:1.5em}.SkXS_7p2VPLT73VFRBky{background-color:#0000;border-radius:5px;padding:.5rem}.SkXS_7p2VPLT73VFRBky:hover{cursor:pointer}.SkXS_7p2VPLT73VFRBky .OLbojYNxmUgjVLX8SD7b{margin-left:1em}
|
|
|
@ -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}
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunkfont_awesome_admin=self.webpackChunkfont_awesome_admin||[]).push([[438],{5325:(r,e,n)=>{var t=n(6131);r.exports=function(r,e){return!(null==r||!r.length)&&t(r,e,0)>-1}},9905:r=>{r.exports=function(r,e,n){for(var t=-1,c=null==r?0:r.length;++t<c;)if(n(e,r[t]))return!0;return!1}},3915:(r,e,n)=>{var t=n(8859),c=n(5325),i=n(9905),o=n(4932),a=n(7301),f=n(9219);r.exports=function(r,e,n,u){var s=-1,l=c,v=!0,p=r.length,x=[],h=e.length;if(!p)return x;n&&(e=o(e,a(n))),u?(l=i,v=!1):e.length>=200&&(l=f,v=!1,e=new t(e));r:for(;++s<p;){var g=r[s],m=null==n?g:n(g);if(g=u||0!==g?g:0,v&&m==m){for(var z=h;z--;)if(e[z]===m)continue r;x.push(g)}else l(e,m,u)||x.push(g)}return x}},6131:(r,e,n)=>{var t=n(2523),c=n(5463),i=n(6959);r.exports=function(r,e,n){return e==e?i(r,e,n):t(r,c,n)}},5463:r=>{r.exports=function(r){return r!=r}},1437:(r,e,n)=>{var t=n(2552),c=n(346);r.exports=function(r){return c(r)&&"[object RegExp]"==t(r)}},9302:(r,e,n)=>{var t=n(3488),c=n(6757),i=n(2865);r.exports=function(r,e){return i(c(r,e,t),r+"")}},8024:(r,e,n)=>{var t=n(5288);r.exports=function(r,e){for(var n=-1,c=r.length,i=0,o=[];++n<c;){var a=r[n],f=e?e(a):a;if(!n||!t(f,u)){var u=f;o[i++]=0===a?0:a}}return o}},6959:r=>{r.exports=function(r,e,n){for(var t=n-1,c=r.length;++t<c;)if(r[t]===e)return t;return-1}},6245:(r,e,n)=>{var t=n(3915),c=n(3120),i=n(9302),o=n(3693),a=i((function(r,e){return o(r)?t(r,c(e,1,o,!0)):[]}));r.exports=a},3693:(r,e,n)=>{var t=n(4894),c=n(346);r.exports=function(r){return c(r)&&t(r)}},2404:(r,e,n)=>{var t=n(270);r.exports=function(r,e){return t(r,e)}},9607:(r,e,n)=>{var t=n(1437),c=n(7301),i=n(6009),o=i&&i.isRegExp,a=o?c(o):t;r.exports=a},3054:(r,e,n)=>{var t=n(8024);r.exports=function(r){return r&&r.length?t(r):[]}},2516:(r,e,n)=>{var t=n(7556),c=n(8754),i=n(9698),o=n(3805),a=n(9607),f=n(1993),u=n(3912),s=n(1489),l=n(3222),v=/\w*$/;r.exports=function(r,e){var n=30,p="...";if(o(e)){var x="separator"in e?e.separator:x;n="length"in e?s(e.length):n,p="omission"in e?t(e.omission):p}var h=(r=l(r)).length;if(i(r)){var g=u(r);h=g.length}if(n>=h)return r;var m=n-f(p);if(m<1)return p;var z=g?c(g,0,m).join(""):r.slice(0,m);if(void 0===x)return z+p;if(g&&(m+=z.length-m),a(x)){if(r.slice(m).search(x)){var M,d=z;for(x.global||(x=RegExp(x.source,l(v.exec(x))+"g")),x.lastIndex=0;M=x.exec(d);)var w=M.index;z=z.slice(0,void 0===w?m:w)}}else if(r.indexOf(t(x),m)!=m){var k=z.lastIndexOf(x);k>-1&&(z=z.slice(0,k))}return z+p}},7897:(r,e,n)=>{"use strict";n.d(e,{GEE:()=>i,Nfw:()=>c,SGM:()=>t,wRm:()=>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"]}}}]);
|
|
|
@ -1 +0,0 @@
|
||||||
"use strict";(self.webpackChunkfont_awesome_admin=self.webpackChunkfont_awesome_admin||[]).push([[56],{6056:(e,s,a)=>{a.r(s)}}]);
|
|
|
@ -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 +0,0 @@
|
||||||
"use strict";(self.webpackChunkfont_awesome_admin=self.webpackChunkfont_awesome_admin||[]).push([[875],{2875:(i,t,e)=>{e.r(t),e.d(t,{fa_icon:()=>o});var s=e(858),n=e(9327);let o=class{constructor(i){(0,s.r)(this,i),this.pro=!1,this.loading=!1}componentWillLoad(){if(this.iconUpload)return void(this.iconDefinition={prefix:this.stylePrefix,iconName:this.iconUpload.name,icon:[parseInt(`${this.iconUpload.width}`),parseInt(`${this.iconUpload.height}`),[],this.iconUpload.unicode.toString(16),this.iconUpload.pathData]});if(this.icon)return void(this.iconDefinition=this.icon);if(!this.svgApi)return void console.error(`${n.C}: fa-icon: svgApi prop is needed but is missing`,this);if(!this.stylePrefix||!this.name)return void console.error(`${n.C}: fa-icon: the 'stylePrefix' and 'name' props are needed to render this icon but not provided.`,this);if(!this.familyStylePathSegment)return void console.error(`${n.C}: fa-icon: the 'familyStylePathSegment' prop is required 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.C}: fa-icon: 'pro' prop is false but no free icon is available`,this);if(!this.svgFetchBaseUrl)return void console.error(`${n.C}: fa-icon: 'svgFetchBaseUrl' prop is absent but is necessary for fetching icon`,this);if(!this.kitToken)return void console.error(`${n.C}: fa-icon: 'kitToken' prop is absent but is necessary for accessing icon`,this);this.loading=!0;const e=`${this.svgFetchBaseUrl}/${this.familyStylePathSegment}/${this.name}.svg?token=${this.kitToken}`,s=n.l.get(this,"svgApi.library");"function"==typeof this.getUrlText?this.getUrlText(e).then((i=>{const t={iconName:this.name,prefix:this.stylePrefix,icon:(0,n.p)(i)};s&&s.add(t),this.iconDefinition=Object.assign({},t)})).catch((i=>{console.error(`${n.C}: fa-icon: failed when using 'getUrlText' to fetch icon`,i,this)})).finally((()=>{this.loading=!1})):console.error(`${n.C}: fa-icon: 'getUrlText' prop is absent but is necessary for fetching icon`,this)}buildSvg(i,t){if(!i)return;const[e,o,,,r]=n.l.get(i,"icon",[]),h=["svg-inline--fa"];this.class&&h.push(this.class),t&&h.push(t),this.size&&h.push(`fa-${this.size}`);const a=h.join(" ");return Array.isArray(r)?(0,s.h)("svg",{class:a,xmlns:"http://www.w3.org/2000/svg",viewBox:`0 0 ${e} ${o}`},(0,s.h)("path",{fill:"currentColor",class:"fa-primary",d:r[1]}),(0,s.h)("path",{fill:"currentColor",class:"fa-secondary",d:r[0]})):(0,s.h)("svg",{class:a,xmlns:"http://www.w3.org/2000/svg",viewBox:`0 0 ${e} ${o}`},(0,s.h)("path",{fill:"currentColor",d:r}))}render(){return this.iconDefinition?this.buildSvg(this.iconDefinition):(0,s.h)(s.f,null)}};o.style=""}}]);
|
|
|
@ -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"]}}}]);
|
|
@ -0,0 +1 @@
|
||||||
|
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-dom-ready', 'wp-element', 'wp-i18n'), 'version' => 'c533036ab18a17f3f9bc');
|
|
@ -5,4 +5,4 @@ process.env.WP_BASE_URL = `http://${process.env.WP_DOMAIN}`
|
||||||
process.env.WP_USERNAME = process.env.WP_ADMIN_USERNAME
|
process.env.WP_USERNAME = process.env.WP_ADMIN_USERNAME
|
||||||
process.env.WP_PASSWORD = process.env.WP_ADMIN_PASSWORD
|
process.env.WP_PASSWORD = process.env.WP_ADMIN_PASSWORD
|
||||||
|
|
||||||
module.exports = defaultConfig;
|
module.exports = defaultConfig
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "font-awesome-admin",
|
"name": "font-awesome-admin",
|
||||||
"version": "4.5.0",
|
"version": "5.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fa-icon-chooser-react": "^0.7.0",
|
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.2.0",
|
"@fortawesome/free-regular-svg-icons": "^6.2.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.3",
|
"@fortawesome/react-fontawesome": "^0.1.3",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.4",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"moment-timezone": "^0.5.45",
|
"moment-timezone": "^0.5.45",
|
||||||
|
@ -19,7 +18,7 @@
|
||||||
"web-vitals": "^4.0.1"
|
"web-vitals": "^4.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wp-scripts build --webpack-no-externals",
|
"build": "wp-scripts build",
|
||||||
"check-engines": "wp-scripts check-engines",
|
"check-engines": "wp-scripts check-engines",
|
||||||
"check-licenses": "wp-scripts check-licenses",
|
"check-licenses": "wp-scripts check-licenses",
|
||||||
"format": "wp-scripts format",
|
"format": "wp-scripts format",
|
||||||
|
@ -29,7 +28,7 @@
|
||||||
"lint:md:js": "wp-scripts lint-md-js",
|
"lint:md:js": "wp-scripts lint-md-js",
|
||||||
"lint:pkg-json": "wp-scripts lint-pkg-json",
|
"lint:pkg-json": "wp-scripts lint-pkg-json",
|
||||||
"packages-update": "wp-scripts packages-update",
|
"packages-update": "wp-scripts packages-update",
|
||||||
"start": "wp-scripts start --webpack-no-externals",
|
"start": "wp-scripts start",
|
||||||
"test:e2e": "wp-scripts test-e2e",
|
"test:e2e": "wp-scripts test-e2e",
|
||||||
"test:playwright": "npx playwright test",
|
"test:playwright": "npx playwright test",
|
||||||
"test:unit": "wp-scripts test-unit-js",
|
"test:unit": "wp-scripts test-unit-js",
|
||||||
|
@ -54,21 +53,23 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@wordpress/babel-preset-default": "wp-6.5",
|
"@wordpress/babel-preset-default": "wp-6.7",
|
||||||
"@wordpress/block-editor": "wp-6.5",
|
"@wordpress/block-editor": "wp-6.7",
|
||||||
"@wordpress/blocks": "wp-6.5",
|
"@wordpress/blocks": "wp-6.7",
|
||||||
"@wordpress/components": "wp-6.5",
|
"@wordpress/components": "wp-6.7",
|
||||||
"@wordpress/compose": "wp-6.5",
|
"@wordpress/compose": "wp-6.7",
|
||||||
"@wordpress/data": "wp-6.5",
|
"@wordpress/data": "wp-6.7",
|
||||||
"@wordpress/e2e-test-utils": "wp-6.5",
|
"@wordpress/e2e-test-utils": "wp-6.7",
|
||||||
"@wordpress/e2e-test-utils-playwright": "^0.26.0",
|
"@wordpress/e2e-test-utils-playwright": "^0.26.0",
|
||||||
"@wordpress/i18n": "wp-6.5",
|
"@wordpress/i18n": "wp-6.7",
|
||||||
"@wordpress/icons": "wp-6.5",
|
"@wordpress/icons": "wp-6.7",
|
||||||
"@wordpress/jest-preset-default": "wp-6.5",
|
"@wordpress/jest-preset-default": "wp-6.7",
|
||||||
"@wordpress/keyboard-shortcuts": "wp-6.5",
|
"@wordpress/keyboard-shortcuts": "wp-6.7",
|
||||||
"@wordpress/scripts": "wp-6.5",
|
"@wordpress/scripts": "wp-6.7",
|
||||||
"decode-uri-component": "^0.4.1",
|
"decode-uri-component": "^0.4.1",
|
||||||
"dotenv": "^14.3.2",
|
"dotenv": "^14.3.2",
|
||||||
|
"eslint-config-react-app": "^7.0.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"mysql2": "^3.9.9",
|
"mysql2": "^3.9.9",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import './src/playwright/support/env.js'
|
import './src/playwright/support/env.js'
|
||||||
import { defineConfig, devices } from '@playwright/test';
|
import { defineConfig, devices } from '@playwright/test'
|
||||||
|
|
||||||
const testDir = 'src/playwright'
|
const testDir = 'src/playwright'
|
||||||
const baseURL = `http://${process.env.WP_DOMAIN}`
|
const baseURL = `http://${process.env.WP_DOMAIN}`
|
||||||
|
@ -8,7 +8,7 @@ const adminStorageStatePath = 'src/playwright/.auth/state.json'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
use: {
|
use: {
|
||||||
baseURL,
|
baseURL
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{ name: 'auth', testDir, testMatch: 'setup/auth.js' },
|
{ name: 'auth', testDir, testMatch: 'setup/auth.js' },
|
||||||
|
@ -18,7 +18,7 @@ export default defineConfig({
|
||||||
testMatch: 'setup/reset.js',
|
testMatch: 'setup/reset.js',
|
||||||
use: {
|
use: {
|
||||||
storageState: adminStorageStatePath
|
storageState: adminStorageStatePath
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'setupProKit',
|
name: 'setupProKit',
|
||||||
|
@ -27,10 +27,7 @@ export default defineConfig({
|
||||||
use: {
|
use: {
|
||||||
storageState: adminStorageStatePath
|
storageState: adminStorageStatePath
|
||||||
},
|
},
|
||||||
dependencies: [
|
dependencies: ['auth', 'reset']
|
||||||
'auth',
|
|
||||||
'reset'
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'with-proKit-chromium',
|
name: 'with-proKit-chromium',
|
||||||
|
@ -39,7 +36,7 @@ export default defineConfig({
|
||||||
...devices['Desktop Chrome'],
|
...devices['Desktop Chrome'],
|
||||||
storageState: adminStorageStatePath
|
storageState: adminStorageStatePath
|
||||||
},
|
},
|
||||||
dependencies: ['setupProKit'],
|
dependencies: ['setupProKit']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'withAuth-chromium',
|
name: 'withAuth-chromium',
|
||||||
|
@ -48,8 +45,7 @@ export default defineConfig({
|
||||||
...devices['Desktop Chrome'],
|
...devices['Desktop Chrome'],
|
||||||
storageState: adminStorageStatePath
|
storageState: adminStorageStatePath
|
||||||
},
|
},
|
||||||
dependencies: ['auth', 'reset'],
|
dependencies: ['auth', 'reset']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -3,51 +3,73 @@ import PropTypes from 'prop-types'
|
||||||
import styles from './Alert.module.css'
|
import styles from './Alert.module.css'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {
|
import { faInfoCircle, faThumbsUp, faSpinner, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'
|
||||||
faInfoCircle,
|
|
||||||
faThumbsUp,
|
|
||||||
faSpinner,
|
|
||||||
faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
|
|
||||||
function getIcon(props = {}) {
|
function getIcon(props = {}) {
|
||||||
switch (props.type) {
|
switch (props.type) {
|
||||||
case 'info':
|
case 'info':
|
||||||
return <FontAwesomeIcon icon={ faInfoCircle } title='info' fixedWidth />
|
return (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faInfoCircle}
|
||||||
|
title="info"
|
||||||
|
fixedWidth
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'warning':
|
case 'warning':
|
||||||
return <FontAwesomeIcon icon={ faExclamationTriangle } title='warning' fixedWidth />
|
return (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faExclamationTriangle}
|
||||||
|
title="warning"
|
||||||
|
fixedWidth
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return <FontAwesomeIcon icon={ faSpinner } title='pending' spin fixedWidth />
|
return (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faSpinner}
|
||||||
|
title="pending"
|
||||||
|
spin
|
||||||
|
fixedWidth
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'success':
|
case 'success':
|
||||||
return <FontAwesomeIcon icon={ faThumbsUp } title='success' fixedWidth />
|
return (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faThumbsUp}
|
||||||
|
title="success"
|
||||||
|
fixedWidth
|
||||||
|
/>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return <FontAwesomeIcon icon={ faExclamationTriangle } title='warning' fixedWidth />
|
return (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faExclamationTriangle}
|
||||||
|
title="warning"
|
||||||
|
fixedWidth
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Alert(props = {}) {
|
function Alert(props = {}) {
|
||||||
return <div className={ classnames(styles['alert'], styles[`alert-${ props.type }`]) } role="alert">
|
return (
|
||||||
<div className={ styles['alert-icon'] }>
|
<div
|
||||||
{ getIcon(props) }
|
className={classnames(styles['alert'], styles[`alert-${props.type}`])}
|
||||||
</div>
|
role="alert"
|
||||||
|
>
|
||||||
|
<div className={styles['alert-icon']}>{getIcon(props)}</div>
|
||||||
<div className={styles['alert-message']}>
|
<div className={styles['alert-message']}>
|
||||||
<h2 className={ styles['alert-title'] }>
|
<h2 className={styles['alert-title']}>{props.title}</h2>
|
||||||
{ props.title }
|
<div className={styles['alert-copy']}>{props.children}</div>
|
||||||
</h2>
|
|
||||||
<div className={ styles['alert-copy'] }>
|
|
||||||
{ props.children }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Alert.propTypes = {
|
Alert.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
type: PropTypes.oneOf(['info', 'warning', 'success', 'pending']),
|
type: PropTypes.oneOf(['info', 'warning', 'success', 'pending']),
|
||||||
children: PropTypes.oneOfType([
|
children: PropTypes.oneOfType([PropTypes.object, PropTypes.string, PropTypes.arrayOf(PropTypes.element)]).isRequired
|
||||||
PropTypes.object,
|
|
||||||
PropTypes.string,
|
|
||||||
PropTypes.arrayOf(PropTypes.element)
|
|
||||||
]).isRequired
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Alert
|
export default Alert
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import {
|
import { addPendingOption, checkPreferenceConflicts } from './store/actions'
|
||||||
addPendingOption,
|
|
||||||
checkPreferenceConflicts
|
|
||||||
} from './store/actions'
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {
|
import { faDotCircle, faCheckSquare, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'
|
||||||
faDotCircle,
|
|
||||||
faCheckSquare,
|
|
||||||
faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import { faCircle, faSquare } from '@fortawesome/free-regular-svg-icons'
|
import { faCircle, faSquare } from '@fortawesome/free-regular-svg-icons'
|
||||||
import styles from './CdnConfigView.module.css'
|
import styles from './CdnConfigView.module.css'
|
||||||
import sharedStyles from './App.module.css'
|
import sharedStyles from './App.module.css'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import has from 'lodash/has'
|
import { has, size, get } from 'lodash'
|
||||||
import size from 'lodash/size'
|
|
||||||
import Alert from './Alert'
|
import Alert from './Alert'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import get from 'lodash/get'
|
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
|
|
||||||
const UNSPECIFIED = ''
|
const UNSPECIFIED = ''
|
||||||
|
@ -29,20 +21,26 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
const compat = useOption('compat')
|
const compat = useOption('compat')
|
||||||
const pseudoElements = useOption('pseudoElements')
|
const pseudoElements = useOption('pseudoElements')
|
||||||
const isVersion6 = !!version.match(/^6\./)
|
const isVersion6 = !!version.match(/^6\./)
|
||||||
|
const isVersion7 = !!version.match(/^7\./)
|
||||||
|
const isVersion5 = !isVersion6 && !isVersion7
|
||||||
|
|
||||||
const pendingOptions = useSelector(state => state.pendingOptions)
|
const pendingOptions = useSelector((state) => state.pendingOptions)
|
||||||
const pendingOptionConflicts = useSelector(state => state.pendingOptionConflicts)
|
const pendingOptionConflicts = useSelector((state) => state.pendingOptionConflicts)
|
||||||
const hasChecked = useSelector(state => state.preferenceConflictDetection.hasChecked)
|
const hasChecked = useSelector((state) => state.preferenceConflictDetection.hasChecked)
|
||||||
const preferenceCheckSuccess = useSelector(state => state.preferenceConflictDetection.success)
|
const preferenceCheckSuccess = useSelector((state) => state.preferenceConflictDetection.success)
|
||||||
const preferenceCheckMessage = useSelector(state => state.preferenceConflictDetection.message)
|
const preferenceCheckMessage = useSelector((state) => state.preferenceConflictDetection.message)
|
||||||
|
|
||||||
const versionOptions = useSelector(state => {
|
const versionOptions = useSelector((state) => {
|
||||||
const { releases: { available, latest_version_5, latest_version_6 } } = state
|
const {
|
||||||
|
releases: { available, latest_version_5, latest_version_6, latest_version_7 }
|
||||||
|
} = state
|
||||||
|
|
||||||
return available.reduce((acc, version) => {
|
return available.reduce((acc, version) => {
|
||||||
if (latest_version_5 === version) {
|
if (latest_version_5 === version) {
|
||||||
acc[version] = `${version} (latest 5.x)`
|
acc[version] = `${version} (latest 5.x)`
|
||||||
} else if (latest_version_6 === version) {
|
} else if (latest_version_6 === version) {
|
||||||
|
acc[version] = `${version} (latest 6.x)`
|
||||||
|
} else if (latest_version_7 === version) {
|
||||||
acc[version] = `${version} (latest)`
|
acc[version] = `${version} (latest)`
|
||||||
} else {
|
} else {
|
||||||
acc[version] = version
|
acc[version] = version
|
||||||
|
@ -69,21 +67,31 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
function getDetectionStatusForOption(option) {
|
function getDetectionStatusForOption(option) {
|
||||||
if (has(pendingOptions, option)) {
|
if (has(pendingOptions, option)) {
|
||||||
if (hasChecked && !preferenceCheckSuccess) {
|
if (hasChecked && !preferenceCheckSuccess) {
|
||||||
return <Alert title={ __( 'Error checking preferences', 'font-awesome' ) } type='warning'>
|
return (
|
||||||
|
<Alert
|
||||||
|
title={__('Error checking preferences', 'font-awesome')}
|
||||||
|
type="warning"
|
||||||
|
>
|
||||||
<p>{preferenceCheckMessage}</p>
|
<p>{preferenceCheckMessage}</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
)
|
||||||
} else if (has(pendingOptionConflicts, option)) {
|
} else if (has(pendingOptionConflicts, option)) {
|
||||||
return <Alert title={ __( 'Preference Conflict', 'font-awesome' ) } type='warning'>
|
return (
|
||||||
{
|
<Alert
|
||||||
size(pendingOptionConflicts[option]) > 1
|
title={__('Preference Conflict', 'font-awesome')}
|
||||||
? <div>
|
type="warning"
|
||||||
|
>
|
||||||
|
{size(pendingOptionConflicts[option]) > 1 ? (
|
||||||
|
<div>
|
||||||
{__('This change might cause problems for these themes or plugins', 'font-awesome')}: {pendingOptionConflicts[option].join(', ')}.
|
{__('This change might cause problems for these themes or plugins', 'font-awesome')}: {pendingOptionConflicts[option].join(', ')}.
|
||||||
</div>
|
</div>
|
||||||
: <div>
|
) : (
|
||||||
|
<div>
|
||||||
{__('This change might cause problems for the theme or plugin', 'font-awesome')}: {pendingOptionConflicts[option][0]}.
|
{__('This change might cause problems for the theme or plugin', 'font-awesome')}: {pendingOptionConflicts[option][0]}.
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -92,8 +100,9 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={ classnames(styles['options-setter']) }>
|
return (
|
||||||
<form onSubmit={ e => e.preventDefault() }>
|
<div className={classnames(styles['options-setter'])}>
|
||||||
|
<form onSubmit={(e) => e.preventDefault()}>
|
||||||
<div className={classnames(sharedStyles['flex'], sharedStyles['flex-row'])}>
|
<div className={classnames(sharedStyles['flex'], sharedStyles['flex-row'])}>
|
||||||
<div className={styles['option-header']}>Icons</div>
|
<div className={styles['option-header']}>Icons</div>
|
||||||
<div className={styles['option-choice-container']}>
|
<div className={styles['option-choice-container']}>
|
||||||
|
@ -107,7 +116,10 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
onChange={() => handleOptionChange({ usePro: true })}
|
onChange={() => handleOptionChange({ usePro: true })}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="code_edit_icons_pro" className={ styles['option-label'] }>
|
<label
|
||||||
|
htmlFor="code_edit_icons_pro"
|
||||||
|
className={styles['option-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faDotCircle}
|
icon={faDotCircle}
|
||||||
|
@ -122,9 +134,7 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
fixedWidth
|
fixedWidth
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className={ styles['option-label-text'] }>
|
<span className={styles['option-label-text']}>Pro</span>
|
||||||
Pro
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['option-choice']}>
|
<div className={styles['option-choice']}>
|
||||||
|
@ -136,7 +146,10 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
onChange={() => handleOptionChange({ usePro: false })}
|
onChange={() => handleOptionChange({ usePro: false })}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="code_edit_icons_free" className={ styles['option-label'] }>
|
<label
|
||||||
|
htmlFor="code_edit_icons_free"
|
||||||
|
className={styles['option-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faDotCircle}
|
icon={faDotCircle}
|
||||||
|
@ -151,32 +164,57 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
className={sharedStyles['unchecked-icon']}
|
className={sharedStyles['unchecked-icon']}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className={ styles['option-label-text'] }>
|
<span className={styles['option-label-text']}>Free</span>
|
||||||
Free
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ usePro &&
|
{usePro && (isVersion6 || isVersion7) && (
|
||||||
isVersion6 &&
|
<Alert
|
||||||
<Alert title={ __( 'Heads up! Pro Version 6 is not available from CDN', 'font-awesome' ) } type='warning'>
|
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')}
|
||||||
<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>
|
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>
|
</Alert>
|
||||||
}
|
)}
|
||||||
{ usePro &&
|
{usePro && isVersion5 && (
|
||||||
!isVersion6 &&
|
<Alert
|
||||||
<Alert title={ __( 'Heads up! Pro requires a Font Awesome subscription', 'font-awesome' ) } type='info'>
|
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>
|
<p>And you need to add your WordPress site to the allowed domains for your CDN.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<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>
|
||||||
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Alert>
|
</Alert>
|
||||||
}
|
)}
|
||||||
{getDetectionStatusForOption('usePro')}
|
{getDetectionStatusForOption('usePro')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -194,7 +232,10 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
onChange={() => handleOptionChange({ technology: 'svg' })}
|
onChange={() => handleOptionChange({ technology: 'svg' })}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="code_edit_tech_svg" className={ styles['option-label'] }>
|
<label
|
||||||
|
htmlFor="code_edit_tech_svg"
|
||||||
|
className={styles['option-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faDotCircle}
|
icon={faDotCircle}
|
||||||
|
@ -209,9 +250,7 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
fixedWidth
|
fixedWidth
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className={ styles['option-label-text'] }>
|
<span className={styles['option-label-text']}>{__('SVG', 'font-awesome')}</span>
|
||||||
{ __( 'SVG', 'font-awesome' ) }
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['option-choice']}>
|
<div className={styles['option-choice']}>
|
||||||
|
@ -220,13 +259,18 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
name="code_edit_tech"
|
name="code_edit_tech"
|
||||||
type="radio"
|
type="radio"
|
||||||
checked={technology === 'webfont'}
|
checked={technology === 'webfont'}
|
||||||
onChange={ () => handleOptionChange({
|
onChange={() =>
|
||||||
|
handleOptionChange({
|
||||||
technology: 'webfont',
|
technology: 'webfont',
|
||||||
pseudoElements: false
|
pseudoElements: false
|
||||||
}) }
|
})
|
||||||
|
}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="code_edit_tech_webfont" className={ styles['option-label'] }>
|
<label
|
||||||
|
htmlFor="code_edit_tech_webfont"
|
||||||
|
className={styles['option-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faDotCircle}
|
icon={faDotCircle}
|
||||||
|
@ -243,12 +287,11 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
</span>
|
</span>
|
||||||
<span className={styles['option-label-text']}>
|
<span className={styles['option-label-text']}>
|
||||||
{__('Web Font', 'font-awesome')}
|
{__('Web Font', 'font-awesome')}
|
||||||
{
|
{technology === 'webfont' && (
|
||||||
technology === 'webfont' &&
|
|
||||||
<span className={styles['option-label-explanation']}>
|
<span className={styles['option-label-explanation']}>
|
||||||
{__('CSS Pseudo-elements are enabled by default with Web Font', 'font-awesome')}
|
{__('CSS Pseudo-elements are enabled by default with Web Font', 'font-awesome')}
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -258,8 +301,11 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
</div>
|
</div>
|
||||||
<div className={classnames(sharedStyles['flex'], sharedStyles['flex-row'])}>
|
<div className={classnames(sharedStyles['flex'], sharedStyles['flex-row'])}>
|
||||||
<div className={styles['option-header']}></div>
|
<div className={styles['option-header']}></div>
|
||||||
<div className={ styles['option-choice-container'] } style={{marginTop: '1em'}}>
|
<div
|
||||||
{ technology === 'svg' &&
|
className={styles['option-choice-container']}
|
||||||
|
style={{ marginTop: '1em' }}
|
||||||
|
>
|
||||||
|
{technology === 'svg' && (
|
||||||
<>
|
<>
|
||||||
<input
|
<input
|
||||||
id="code_edit_features_pseudo_elements"
|
id="code_edit_features_pseudo_elements"
|
||||||
|
@ -269,7 +315,10 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
onChange={() => handleOptionChange({ pseudoElements: !pseudoElements })}
|
onChange={() => handleOptionChange({ pseudoElements: !pseudoElements })}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
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']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faCheckSquare}
|
icon={faCheckSquare}
|
||||||
|
@ -287,15 +336,25 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
<span className={styles['option-label-text']}>
|
<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']}>
|
<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">
|
{__('May cause performance issues.', 'font-awesome')}{' '}
|
||||||
{ __( 'Learn more', 'font-awesome' ) } <FontAwesomeIcon icon={faExternalLinkAlt} style={{marginLeft: '.5em'}} />
|
<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>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{getDetectionStatusForOption('pseudoElements')}
|
{getDetectionStatusForOption('pseudoElements')}
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr className={styles['option-divider']} />
|
<hr className={styles['option-divider']} />
|
||||||
|
@ -306,16 +365,19 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
<select
|
<select
|
||||||
className={styles['version-select']}
|
className={styles['version-select']}
|
||||||
name="version"
|
name="version"
|
||||||
onChange={ e => handleOptionChange({ version: e.target.value }) }
|
onChange={(e) => handleOptionChange({ version: e.target.value })}
|
||||||
|
value={version}
|
||||||
|
>
|
||||||
|
{Object.keys(versionOptions).map((version, index) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
key={index}
|
||||||
value={version}
|
value={version}
|
||||||
>
|
>
|
||||||
{
|
|
||||||
Object.keys(versionOptions).map((version, index) => {
|
|
||||||
return <option key={ index } value={ version }>
|
|
||||||
{version === UNSPECIFIED ? '-' : versionOptions[version]}
|
{version === UNSPECIFIED ? '-' : versionOptions[version]}
|
||||||
</option>
|
</option>
|
||||||
})
|
)
|
||||||
}
|
})}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{getDetectionStatusForOption('version')}
|
{getDetectionStatusForOption('version')}
|
||||||
|
@ -336,7 +398,10 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
onChange={() => handleOptionChange({ compat: !compat })}
|
onChange={() => handleOptionChange({ compat: !compat })}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="code_edit_compat_on" className={ styles['option-label'] }>
|
<label
|
||||||
|
htmlFor="code_edit_compat_on"
|
||||||
|
className={styles['option-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faDotCircle}
|
icon={faDotCircle}
|
||||||
|
@ -351,9 +416,7 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
fixedWidth
|
fixedWidth
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className={ styles['option-label-text'] }>
|
<span className={styles['option-label-text']}>{__('On', 'font-awesome')}</span>
|
||||||
{ __( 'On', 'font-awesome' ) }
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['option-choice']}>
|
<div className={styles['option-choice']}>
|
||||||
|
@ -366,7 +429,10 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
onChange={() => handleOptionChange({ compat: !compat })}
|
onChange={() => handleOptionChange({ compat: !compat })}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="code_edit_v4_compat_off" className={ styles['option-label'] }>
|
<label
|
||||||
|
htmlFor="code_edit_v4_compat_off"
|
||||||
|
className={styles['option-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faDotCircle}
|
icon={faDotCircle}
|
||||||
|
@ -381,9 +447,7 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
className={sharedStyles['unchecked-icon']}
|
className={sharedStyles['unchecked-icon']}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className={ styles['option-label-text'] }>
|
<span className={styles['option-label-text']}>{__('Off', 'font-awesome')}</span>
|
||||||
{ __( 'Off', 'font-awesome' ) }
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -392,6 +456,7 @@ export default function CdnConfigView({ useOption, handleSubmit }) {
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
CdnConfigView.propTypes = {
|
CdnConfigView.propTypes = {
|
||||||
|
|
|
@ -7,8 +7,14 @@ import { faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
|
|
||||||
export default function CheckingOptionStatusIndicator() {
|
export default function CheckingOptionStatusIndicator() {
|
||||||
return <span className={ styles['checking-option-status-indicator'] }>
|
return (
|
||||||
<FontAwesomeIcon spin className={ classnames(sharedStyles['icon']) } icon={ faSpinner }/>
|
<span className={styles['checking-option-status-indicator']}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
spin
|
||||||
|
className={classnames(sharedStyles['icon'])}
|
||||||
|
icon={faSpinner}
|
||||||
|
/>
|
||||||
{__('checking for preference conflicts', 'font-awesome')}...
|
{__('checking for preference conflicts', 'font-awesome')}...
|
||||||
</span>
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,7 @@ import React from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import styles from './ClientPreferencesView.module.css'
|
import styles from './ClientPreferencesView.module.css'
|
||||||
import sharedStyles from './App.module.css'
|
import sharedStyles from './App.module.css'
|
||||||
import find from 'lodash/find'
|
import { find, has, size } from 'lodash'
|
||||||
import has from 'lodash/has'
|
|
||||||
import size from 'lodash/size'
|
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { __, sprintf } from '@wordpress/i18n'
|
import { __, sprintf } from '@wordpress/i18n'
|
||||||
|
|
||||||
|
@ -12,7 +10,7 @@ const UNSPECIFIED_INDICATOR = '-'
|
||||||
|
|
||||||
function formatVersionPreference(versionPreference = []) {
|
function formatVersionPreference(versionPreference = []) {
|
||||||
return versionPreference
|
return versionPreference
|
||||||
.map(pref => `${pref[1]}${pref[0]}`)
|
.map((pref) => `${pref[1]}${pref[0]}`)
|
||||||
.join(
|
.join(
|
||||||
sprintf(
|
sprintf(
|
||||||
/* translators: 1: space */
|
/* translators: 1: space */
|
||||||
|
@ -23,31 +21,31 @@ function formatVersionPreference(versionPreference = []) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ClientPreferencesView() {
|
export default function ClientPreferencesView() {
|
||||||
const clientPreferences = useSelector(state => state.clientPreferences)
|
const clientPreferences = useSelector((state) => state.clientPreferences)
|
||||||
const conflicts = useSelector(state => state.preferenceConflicts)
|
const conflicts = useSelector((state) => state.preferenceConflicts)
|
||||||
const hasAdditionalClients = size(clientPreferences)
|
const hasAdditionalClients = size(clientPreferences)
|
||||||
const hasConflicts = size(conflicts)
|
const hasConflicts = size(conflicts)
|
||||||
|
|
||||||
return <div className={ styles['client-requirements'] }>
|
return (
|
||||||
|
<div className={styles['client-requirements']}>
|
||||||
<h3 className={sharedStyles['section-title']}>{__('Registered themes or plugins', 'font-awesome')}</h3>
|
<h3 className={sharedStyles['section-title']}>{__('Registered themes or plugins', 'font-awesome')}</h3>
|
||||||
{
|
{hasAdditionalClients ? (
|
||||||
hasAdditionalClients
|
|
||||||
?
|
|
||||||
<div>
|
<div>
|
||||||
<p className={sharedStyles['explanation']}>
|
<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.',
|
'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'
|
'font-awesome'
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
|
|
||||||
{ hasConflicts
|
{hasConflicts ? (
|
||||||
? <span className={sharedStyles['explanation']}>
|
<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' ) }
|
{__(
|
||||||
|
'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>
|
</span>
|
||||||
: null
|
) : null}
|
||||||
}</p>
|
</p>
|
||||||
<table className={classnames('widefat', 'striped')}>
|
<table className={classnames('widefat', 'striped')}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className={sharedStyles['table-header']}>
|
<tr className={sharedStyles['table-header']}>
|
||||||
|
@ -60,59 +58,36 @@ export default function ClientPreferencesView() {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{Object.values(clientPreferences).map((client, index) => {
|
||||||
Object.values(clientPreferences).map((client, index) => {
|
const clientHasConflict = (optionName) => !!find(conflicts[optionName], (c) => c === client.name)
|
||||||
const clientHasConflict = optionName => !!find(conflicts[optionName], c => c === client.name)
|
|
||||||
|
|
||||||
return <tr key={ index }>
|
return (
|
||||||
|
<tr key={index}>
|
||||||
<td>{client.name}</td>
|
<td>{client.name}</td>
|
||||||
<td
|
<td className={classnames({ [styles.conflicted]: clientHasConflict('usePro') })}>
|
||||||
className={
|
{has(client, 'usePro') ? (client.usePro ? 'Pro' : 'Free') : UNSPECIFIED_INDICATOR}
|
||||||
classnames({ [styles.conflicted]: clientHasConflict('usePro') })
|
|
||||||
}>
|
|
||||||
{ has(client, 'usePro')
|
|
||||||
? client.usePro ? 'Pro' : 'Free'
|
|
||||||
: UNSPECIFIED_INDICATOR
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td className={classnames({ [styles.conflicted]: clientHasConflict('technology') })}>
|
||||||
className={ classnames({ [styles.conflicted]: clientHasConflict('technology') }) }>
|
{has(client, 'technology') ? client.technology : UNSPECIFIED_INDICATOR}
|
||||||
{ has(client, 'technology')
|
|
||||||
? client.technology
|
|
||||||
: UNSPECIFIED_INDICATOR
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td className={classnames({ [styles.conflicted]: clientHasConflict('version') })}>
|
||||||
className={ classnames({ [styles.conflicted]: clientHasConflict('version') }) }>
|
{has(client, 'version') ? formatVersionPreference(client.version) : UNSPECIFIED_INDICATOR}
|
||||||
{ has(client, 'version')
|
|
||||||
? formatVersionPreference(client.version)
|
|
||||||
: UNSPECIFIED_INDICATOR
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td className={classnames({ [styles.conflicted]: clientHasConflict('compat') })}>
|
||||||
className={ classnames({ [styles.conflicted]: clientHasConflict('compat') }) }>
|
{has(client, 'compat') ? (client.compat ? 'true' : 'false') : UNSPECIFIED_INDICATOR}
|
||||||
{ has(client, 'compat')
|
|
||||||
? client.compat ? 'true' : 'false'
|
|
||||||
: UNSPECIFIED_INDICATOR
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td className={classnames({ [styles.conflicted]: clientHasConflict('pseudoElements') })}>
|
||||||
className={ classnames({ [styles.conflicted]: clientHasConflict('pseudoElements') }) }>
|
{has(client, 'pseudoElements') ? (client.pseudoElements ? 'true' : 'false') : UNSPECIFIED_INDICATOR}
|
||||||
{ has(client, 'pseudoElements')
|
|
||||||
? client.pseudoElements ? 'true' : 'false'
|
|
||||||
: UNSPECIFIED_INDICATOR
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
})
|
)
|
||||||
}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
:
|
) : (
|
||||||
<p className={ sharedStyles['explanation'] }>
|
<p className={sharedStyles['explanation']}>{__('No active themes or plugins have requested preferences for Font Awesome.', 'font-awesome')}</p>
|
||||||
{ __( 'No active themes or plugins have requested preferences for Font Awesome.', 'font-awesome' ) }
|
)}
|
||||||
</p>
|
|
||||||
}
|
|
||||||
</div>
|
</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 { faCheckCircle, faCog, faExclamationTriangle, faGrin, faSkull, faThumbsUp, faTimesCircle } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ADMIN_TAB_TROUBLESHOOT } from './store/reducers'
|
import { ADMIN_TAB_TROUBLESHOOT } from './store/reducers'
|
||||||
import ConflictDetectionTimer from './ConflictDetectionTimer'
|
import ConflictDetectionTimer from './ConflictDetectionTimer'
|
||||||
import size from 'lodash/size'
|
import { has, size } from 'lodash'
|
||||||
import has from 'lodash/has'
|
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
import ErrorBoundary from './ErrorBoundary'
|
import ErrorBoundary from './ErrorBoundary'
|
||||||
|
|
||||||
|
@ -147,67 +146,53 @@ const STYLES = {
|
||||||
function withErrorBoundary(Component) {
|
function withErrorBoundary(Component) {
|
||||||
return class extends ErrorBoundary {
|
return class extends ErrorBoundary {
|
||||||
render() {
|
render() {
|
||||||
return <div style={ STYLES.container }>
|
return (
|
||||||
{
|
<div style={STYLES.container}>
|
||||||
!!this.state.error
|
{!!this.state.error ? (
|
||||||
? <div style={ STYLES.badness }>
|
<div style={STYLES.badness}>
|
||||||
<FontAwesomeIcon icon={faExclamationTriangle} />
|
<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' )
|
' Whoops, this is embarrassing! Some unexpected error has occurred. There might be some additional diagnostic information in the JavaScript console.',
|
||||||
}
|
'font-awesome'
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
: <Component />
|
) : (
|
||||||
}
|
<Component />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConflictDetectionReporter() {
|
function ConflictDetectionReporter() {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const settingsPageUrl = useSelector(state => state.settingsPageUrl)
|
const settingsPageUrl = useSelector((state) => state.settingsPageUrl)
|
||||||
const troubleshootTabUrl = `${settingsPageUrl}&tab=ts`
|
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 currentlyOnPluginAdminPage = window.location.href.startsWith(settingsPageUrl)
|
||||||
const currentlyOnTroubleshootTab = currentlyOnPluginAdminPage && activeAdminTab === ADMIN_TAB_TROUBLESHOOT
|
const currentlyOnTroubleshootTab = currentlyOnPluginAdminPage && activeAdminTab === ADMIN_TAB_TROUBLESHOOT
|
||||||
const userAttemptedToStopScanner = useSelector(state => state.userAttemptedToStopScanner)
|
const userAttemptedToStopScanner = useSelector((state) => state.userAttemptedToStopScanner)
|
||||||
|
|
||||||
const unregisteredClients = useSelector(
|
const unregisteredClients = useSelector((state) => state.unregisteredClients)
|
||||||
state => state.unregisteredClients
|
|
||||||
)
|
|
||||||
|
|
||||||
const unregisteredClientsBeforeDetection = useSelector(
|
const unregisteredClientsBeforeDetection = useSelector((state) => state.unregisteredClientDetectionStatus.unregisteredClientsBeforeDetection)
|
||||||
state => state.unregisteredClientDetectionStatus.unregisteredClientsBeforeDetection
|
|
||||||
)
|
|
||||||
|
|
||||||
const recentConflictsDetected = useSelector(
|
const recentConflictsDetected = useSelector((state) => state.unregisteredClientDetectionStatus.recentConflictsDetected)
|
||||||
state => state.unregisteredClientDetectionStatus.recentConflictsDetected
|
|
||||||
)
|
|
||||||
|
|
||||||
const expired = useSelector(
|
const expired = useSelector((state) => !state.showConflictDetectionReporter)
|
||||||
state => !state.showConflictDetectionReporter
|
|
||||||
)
|
|
||||||
|
|
||||||
const restarting = useSelector(
|
const restarting = useSelector((state) => expired && state.conflictDetectionScannerStatus.isSubmitting)
|
||||||
state => expired && state.conflictDetectionScannerStatus.isSubmitting
|
|
||||||
)
|
|
||||||
|
|
||||||
const scannerReady = useSelector(
|
const scannerReady = useSelector((state) => state.conflictDetectionScannerStatus.hasSubmitted && state.conflictDetectionScannerStatus.success)
|
||||||
state => state.conflictDetectionScannerStatus.hasSubmitted && state.conflictDetectionScannerStatus.success
|
|
||||||
)
|
|
||||||
|
|
||||||
const scannerIsStopping = useSelector(
|
const scannerIsStopping = useSelector((state) => userAttemptedToStopScanner && !state.conflictDetectionScannerStatus.hasSubmitted)
|
||||||
state => userAttemptedToStopScanner
|
|
||||||
&& !state.conflictDetectionScannerStatus.hasSubmitted
|
|
||||||
)
|
|
||||||
|
|
||||||
const userStoppedScannerSuccessfully = useSelector(
|
const userStoppedScannerSuccessfully = useSelector(
|
||||||
state => userAttemptedToStopScanner
|
(state) => userAttemptedToStopScanner && !scannerIsStopping && state.conflictDetectionScannerStatus.success
|
||||||
&& !scannerIsStopping
|
|
||||||
&& state.conflictDetectionScannerStatus.success
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const runStatus = useSelector(state => {
|
const runStatus = useSelector((state) => {
|
||||||
const { isSubmitting, hasSubmitted, success } = state.unregisteredClientDetectionStatus
|
const { isSubmitting, hasSubmitted, success } = state.unregisteredClientDetectionStatus
|
||||||
if (userAttemptedToStopScanner) {
|
if (userAttemptedToStopScanner) {
|
||||||
if (scannerIsStopping) {
|
if (scannerIsStopping) {
|
||||||
|
@ -238,37 +223,50 @@ function ConflictDetectionReporter() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const errorMessage = useSelector(
|
const errorMessage = useSelector((state) => state.unregisteredClientDetectionStatus.message)
|
||||||
state => state.unregisteredClientDetectionStatus.message
|
|
||||||
)
|
|
||||||
|
|
||||||
function stopScanner() {
|
function stopScanner() {
|
||||||
dispatch(userAttemptToStopScanner())
|
dispatch(userAttemptToStopScanner())
|
||||||
dispatch(setConflictDetectionScanner({ enable: false }))
|
dispatch(setConflictDetectionScanner({ enable: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const expiredOrStoppedDiv =
|
const expiredOrStoppedDiv = (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={ STYLES.tally }><span>{ size( unregisteredClients ) }</span> <span> { __( 'Results to Review', 'font-awesome' ) }</span></h2>
|
<h2 style={STYLES.tally}>
|
||||||
|
<span>{size(unregisteredClients)}</span> <span> {__('Results to Review', 'font-awesome')}</span>
|
||||||
|
</h2>
|
||||||
<p style={STYLES.p}>
|
<p style={STYLES.p}>
|
||||||
{
|
{currentlyOnTroubleshootTab ? (
|
||||||
currentlyOnTroubleshootTab
|
__('Manage results or restart the scanner here on the Troubleshoot tab.', 'font-awesome')
|
||||||
? __( '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')}{' '}
|
||||||
__( 'Manage results or restart the scanner on the Troubleshoot tab.', 'font-awesome' )
|
<a
|
||||||
} <a href={ troubleshootTabUrl } style={ STYLES.link }>{ __('Go', 'font-awesome' ) }</a>
|
href={troubleshootTabUrl}
|
||||||
|
style={STYLES.link}
|
||||||
|
>
|
||||||
|
{__('Go', 'font-awesome')}
|
||||||
|
</a>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const stoppingOrSubmittingDiv =
|
const stoppingOrSubmittingDiv = (
|
||||||
<div>
|
<div>
|
||||||
<div style={STYLES.status}>
|
<div style={STYLES.status}>
|
||||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faCog } size="sm" spin /> <span>{ runStatus.display }</span></h2>
|
<h2 style={STYLES.h2}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCog}
|
||||||
|
size="sm"
|
||||||
|
spin
|
||||||
|
/>{' '}
|
||||||
|
<span>{runStatus.display}</span>
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -277,68 +275,125 @@ function ConflictDetectionReporter() {
|
||||||
<p style={STYLES.adminEyesOnly}>{__('only admins can see this box', 'font-awesome')}</p>
|
<p style={STYLES.adminEyesOnly}>{__('only admins can see this box', 'font-awesome')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div style={STYLES.content}>
|
<div style={STYLES.content}>
|
||||||
|
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
None:
|
None: (
|
||||||
<div>
|
<div>
|
||||||
<div style={STYLES.status}>
|
<div style={STYLES.status}>
|
||||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faGrin } size="sm" /> <span>{ __( 'All clear!', 'font-awesome' ) }</span></h2>
|
<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>
|
<p style={STYLES.p}>{__('No new conflicts found on this page.', 'font-awesome')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>
|
||||||
Running:
|
),
|
||||||
|
Running: (
|
||||||
<div>
|
<div>
|
||||||
<div style={STYLES.status}>
|
<div style={STYLES.status}>
|
||||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faCog } size="sm" spin /> <span>{ __( 'Scanning', 'font-awesome' ) }...</span></h2>
|
<h2 style={STYLES.h2}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCog}
|
||||||
|
size="sm"
|
||||||
|
spin
|
||||||
|
/>{' '}
|
||||||
|
<span>{__('Scanning', 'font-awesome')}...</span>
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>
|
||||||
Restarting:
|
),
|
||||||
|
Restarting: (
|
||||||
<div>
|
<div>
|
||||||
<div style={STYLES.status}>
|
<div style={STYLES.status}>
|
||||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faCog } size="sm" spin /> <span>{ __( 'Restarting', 'font-awesome' ) }...</span></h2>
|
<h2 style={STYLES.h2}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCog}
|
||||||
|
size="sm"
|
||||||
|
spin
|
||||||
|
/>{' '}
|
||||||
|
<span>{__('Restarting', 'font-awesome')}...</span>
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>
|
||||||
Ready:
|
),
|
||||||
|
Ready: (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faThumbsUp } size="sm" /> { __( 'Proton pack charged!', 'font-awesome' ) }</h2>
|
<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>
|
<p style={STYLES.p}>{__('Wander through the pages of your web site and this scanner will track progress.', 'font-awesome')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>
|
||||||
|
),
|
||||||
Submitting: stoppingOrSubmittingDiv,
|
Submitting: stoppingOrSubmittingDiv,
|
||||||
Stopping: stoppingOrSubmittingDiv,
|
Stopping: stoppingOrSubmittingDiv,
|
||||||
Done:
|
Done: (
|
||||||
<div>
|
<div>
|
||||||
<div style={STYLES.status}>
|
<div style={STYLES.status}>
|
||||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faCheckCircle } size="sm" /> <span>{ __( 'Page scan complete', 'font-awesome' ) }</span></h2>
|
<h2 style={STYLES.h2}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCheckCircle}
|
||||||
|
size="sm"
|
||||||
|
/>{' '}
|
||||||
|
<span>{__('Page scan complete', 'font-awesome')}</span>
|
||||||
|
</h2>
|
||||||
</div>
|
</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}>
|
||||||
<p style={ STYLES.tally }><span style={ STYLES.count }>{ size( unregisteredClients ) }</span> <span>total found</span>
|
<span style={STYLES.count}>{size(Object.keys(recentConflictsDetected).filter((k) => !has(unregisteredClientsBeforeDetection, k)))}</span>{' '}
|
||||||
{
|
<span>{__('new conflicts found on this page', 'font-awesome')}</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>
|
</p>
|
||||||
</div>,
|
<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,
|
Expired: expiredOrStoppedDiv,
|
||||||
Stopped: expiredOrStoppedDiv,
|
Stopped: expiredOrStoppedDiv,
|
||||||
Error:
|
Error: (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={ STYLES.h2 }><FontAwesomeIcon icon={ faSkull } /> <span>{ __( 'Don\'t cross the streams! It would be bad.', 'font-awesome' ) }</span></h2>
|
<h2 style={STYLES.h2}>
|
||||||
<p style={ STYLES.p }>
|
<FontAwesomeIcon icon={faSkull} /> <span>{__("Don't cross the streams! It would be bad.", 'font-awesome')}</span>
|
||||||
{ errorMessage }
|
</h2>
|
||||||
</p>
|
<p style={STYLES.p}>{errorMessage}</p>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}[runStatus.code]
|
}[runStatus.code]
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div style={STYLES.timerRow}>
|
<div style={STYLES.timerRow}>
|
||||||
<span>
|
<span>
|
||||||
<ConflictDetectionTimer addDescription>
|
<ConflictDetectionTimer addDescription>
|
||||||
<button style={ STYLES.button } title={ __( 'Stop timer', 'font-awesome' ) } onClick={() => stopScanner()}>
|
<button
|
||||||
<FontAwesomeIcon icon={ faTimesCircle } size="lg" />
|
style={STYLES.button}
|
||||||
|
title={__('Stop timer', 'font-awesome')}
|
||||||
|
onClick={() => stopScanner()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faTimesCircle}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</ConflictDetectionTimer>
|
</ConflictDetectionTimer>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -11,11 +11,11 @@ import createInterpolateElement from './createInterpolateElement'
|
||||||
|
|
||||||
export default function ConflictDetectionScannerSection() {
|
export default function ConflictDetectionScannerSection() {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const detectConflictsUntil = useSelector(state => state.detectConflictsUntil)
|
const detectConflictsUntil = useSelector((state) => state.detectConflictsUntil)
|
||||||
const nowMs = (new Date()).valueOf()
|
const nowMs = new Date().valueOf()
|
||||||
const detectingConflicts = (new Date(detectConflictsUntil * 1000)) > nowMs
|
const detectingConflicts = new Date(detectConflictsUntil * 1000) > nowMs
|
||||||
const { isSubmitting, hasSubmitted, message, success } = useSelector(state => state.conflictDetectionScannerStatus)
|
const { isSubmitting, hasSubmitted, message, success } = useSelector((state) => state.conflictDetectionScannerStatus)
|
||||||
const showConflictDetectionReporter = useSelector(state => state.showConflictDetectionReporter)
|
const showConflictDetectionReporter = useSelector((state) => state.showConflictDetectionReporter)
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -31,51 +31,64 @@ export default function ConflictDetectionScannerSection() {
|
||||||
}
|
}
|
||||||
}, [showConflictDetectionReporter, store])
|
}, [showConflictDetectionReporter, store])
|
||||||
|
|
||||||
return <div>
|
return (
|
||||||
|
<div>
|
||||||
<h2 className={sharedStyles['section-title']}>{__('Detect Conflicts with Other Versions of Font Awesome', 'font-awesome')}</h2>
|
<h2 className={sharedStyles['section-title']}>{__('Detect Conflicts with Other Versions of Font Awesome', 'font-awesome')}</h2>
|
||||||
<div className={sharedStyles['explanation']}>
|
<div className={sharedStyles['explanation']}>
|
||||||
<p>
|
<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' ) }
|
{__(
|
||||||
|
'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>
|
||||||
|
|
||||||
<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'
|
||||||
|
),
|
||||||
{
|
{
|
||||||
createInterpolateElement(
|
noWrap: <span style={{ whiteSpace: 'nowrap' }} />
|
||||||
__( '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" }} />
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={sharedStyles['scanner-actions']}>
|
<div className={sharedStyles['scanner-actions']}>
|
||||||
{
|
{detectingConflicts ? (
|
||||||
detectingConflicts
|
<button
|
||||||
? <button className={sharedStyles['faPrimary']} disabled >
|
className={sharedStyles['faPrimary']}
|
||||||
|
disabled
|
||||||
|
>
|
||||||
{__('Scanner running', 'font-awesome')}: <ConflictDetectionTimer />
|
{__('Scanner running', 'font-awesome')}: <ConflictDetectionTimer />
|
||||||
</button>
|
</button>
|
||||||
: <button className="button button-primary" disabled={ isSubmitting } onClick={() => dispatch(setConflictDetectionScanner({ enable: true }))}>
|
) : (
|
||||||
{
|
<button
|
||||||
sprintf(
|
className="button button-primary"
|
||||||
__( 'Enable scanner for %d minutes', 'font-awesome' ),
|
disabled={isSubmitting}
|
||||||
CONFLICT_DETECTION_SCANNER_DURATION_MIN
|
onClick={() => dispatch(setConflictDetectionScanner({ enable: true }))}
|
||||||
)
|
>
|
||||||
}
|
{sprintf(__('Enable scanner for %d minutes', 'font-awesome'), CONFLICT_DETECTION_SCANNER_DURATION_MIN)}
|
||||||
</button>
|
</button>
|
||||||
}
|
)}
|
||||||
<div className={sharedStyles['scanner-runstatus']}>
|
<div className={sharedStyles['scanner-runstatus']}>
|
||||||
{
|
{isSubmitting ? (
|
||||||
isSubmitting
|
<FontAwesomeIcon
|
||||||
? <FontAwesomeIcon icon={ faSpinner } spin />
|
icon={faSpinner}
|
||||||
: hasSubmitted
|
spin
|
||||||
? success
|
/>
|
||||||
? <FontAwesomeIcon icon={ faCheck } />
|
) : hasSubmitted ? (
|
||||||
: <><FontAwesomeIcon icon={ faSkull } /> <span>{ message }</span></>
|
success ? (
|
||||||
: null
|
<FontAwesomeIcon icon={faCheck} />
|
||||||
}
|
) : (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faSkull} /> <span>{message}</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr className={sharedStyles['section-divider']} />
|
<hr className={sharedStyles['section-divider']} />
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ import React, { useState, useEffect } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import sharedStyles from './App.module.css'
|
import sharedStyles from './App.module.css'
|
||||||
import padStart from 'lodash/padStart'
|
import { padStart, dropWhile } from 'lodash'
|
||||||
import dropWhile from 'lodash/dropWhile'
|
|
||||||
import { __, sprintf } from '@wordpress/i18n'
|
import { __, sprintf } from '@wordpress/i18n'
|
||||||
|
|
||||||
const SECONDS_PER_DAY = 60 * 60 * 24
|
const SECONDS_PER_DAY = 60 * 60 * 24
|
||||||
|
@ -12,7 +11,7 @@ const SECONDS_PER_MINUTE = 60
|
||||||
|
|
||||||
export function timerString(durationSeconds) {
|
export function timerString(durationSeconds) {
|
||||||
const days = Math.floor(durationSeconds / SECONDS_PER_DAY)
|
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 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)
|
const seconds = durationSeconds - (days * SECONDS_PER_DAY + hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE)
|
||||||
|
|
||||||
|
@ -25,19 +24,19 @@ export function timerString(durationSeconds) {
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
}, []),
|
}, []),
|
||||||
part => part.match(/^[0]+$/)
|
(part) => part.match(/^[0]+$/)
|
||||||
).join(':')
|
).join(':')
|
||||||
}
|
}
|
||||||
|
|
||||||
function secondsRemaining(endTime) {
|
function secondsRemaining(endTime) {
|
||||||
const now = Math.floor((new Date()) / 1000)
|
const now = Math.floor(new Date() / 1000)
|
||||||
const remaining = endTime - now
|
const remaining = endTime - now
|
||||||
|
|
||||||
return remaining < 0 ? 0 : remaining
|
return remaining < 0 ? 0 : remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ConflictDetectionTimer({ addDescription, children }) {
|
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 [timeRemaining, setTimer] = useState(secondsRemaining(detectConflictsUntil))
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
@ -56,22 +55,18 @@ export default function ConflictDetectionTimer({ addDescription, children }) {
|
||||||
return () => timeoutId && clearTimeout(timeoutId)
|
return () => timeoutId && clearTimeout(timeoutId)
|
||||||
}, [detectConflictsUntil, timeRemaining, dispatch])
|
}, [detectConflictsUntil, timeRemaining, dispatch])
|
||||||
|
|
||||||
return timeRemaining <= 0 ? null : <span className={ sharedStyles['conflict-detection-timer'] }>
|
return timeRemaining <= 0 ? null : (
|
||||||
|
<span className={sharedStyles['conflict-detection-timer']}>
|
||||||
{timerString(timeRemaining)}
|
{timerString(timeRemaining)}
|
||||||
{
|
{!!addDescription &&
|
||||||
!!addDescription &&
|
(timeRemaining > 60
|
||||||
(
|
? /* translators: 1: space */
|
||||||
timeRemaining > 60
|
sprintf(__('%1$sminutes left to browse your site for trouble', 'font-awesome'), ' ')
|
||||||
/* translators: 1: space */
|
: /* translators: 1: space */
|
||||||
? sprintf( __( '%1$sminutes left to browse your site for trouble', 'font-awesome' ), ' ' )
|
sprintf(__('%1$sseconds left to browse your site for trouble', 'font-awesome'), ' '))}
|
||||||
/* translators: 1: space */
|
{children}
|
||||||
: sprintf( __( '%1$sseconds left to browse your site for trouble', 'font-awesome' ), ' ' )
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
children
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ConflictDetectionTimer.propTypes = {
|
ConflictDetectionTimer.propTypes = {
|
||||||
|
|
|
@ -3,18 +3,17 @@ import styles from './ErrorFallbackView.module.css'
|
||||||
import Alert from './Alert'
|
import Alert from './Alert'
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
|
|
||||||
export const fatalAlert = <Alert title={ __( 'Whoops, this is embarrassing', 'font-awesome' ) } type='warning'>
|
export const fatalAlert = (
|
||||||
<p>
|
<Alert
|
||||||
{
|
title={__('Whoops, this is embarrassing', 'font-awesome')}
|
||||||
__( 'Some unexpected error has occurred. There might be some additional diagnostic information in the JavaScript console.', 'font-awesome' )
|
type="warning"
|
||||||
}
|
>
|
||||||
</p>
|
<p>{__('Some unexpected error has occurred. There might be some additional diagnostic information in the JavaScript console.', 'font-awesome')}</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
)
|
||||||
|
|
||||||
function ErrorFallbackView() {
|
function ErrorFallbackView() {
|
||||||
return <div className={ styles['error-fallback'] }>
|
return <div className={styles['error-fallback']}>{fatalAlert}</div>
|
||||||
{ fatalAlert }
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ErrorFallbackView
|
export default ErrorFallbackView
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { setActiveAdminTab } from './store/actions'
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
|
|
||||||
export default function FontAwesomeAdminView() {
|
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()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,70 +5,57 @@ import styles from './KitSelectView.module.css'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import Alert from './Alert'
|
import Alert from './Alert'
|
||||||
import get from 'lodash/get'
|
import { get, has, size } from 'lodash'
|
||||||
import has from 'lodash/has'
|
|
||||||
import size from 'lodash/size'
|
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
import createInterpolateElement from './createInterpolateElement'
|
import createInterpolateElement from './createInterpolateElement'
|
||||||
|
|
||||||
export default function KitConfigView({ kitToken }) {
|
export default function KitConfigView({ kitToken }) {
|
||||||
const kitTokenIsActive = useSelector(state => get(state, 'options.kitToken') === kitToken)
|
const kitTokenIsActive = useSelector((state) => get(state, 'options.kitToken') === kitToken)
|
||||||
const kitTokenApiData = useSelector(state => (state.kits || []).find(k => k.token === kitToken))
|
const kitTokenApiData = useSelector((state) => (state.kits || []).find((k) => k.token === kitToken))
|
||||||
const pendingOptionConflicts = useSelector(state => state.pendingOptionConflicts)
|
const pendingOptionConflicts = useSelector((state) => state.pendingOptionConflicts)
|
||||||
const hasChecked = useSelector(state => state.preferenceConflictDetection.hasChecked)
|
const hasChecked = useSelector((state) => state.preferenceConflictDetection.hasChecked)
|
||||||
const preferenceCheckSuccess = useSelector(state => state.preferenceConflictDetection.success)
|
const preferenceCheckSuccess = useSelector((state) => state.preferenceConflictDetection.success)
|
||||||
|
|
||||||
const technology = useSelector(state =>
|
const technology = useSelector((state) => (kitTokenIsActive ? state.options.technology : kitTokenApiData.technologySelected === 'svg' ? 'svg' : 'webfont'))
|
||||||
kitTokenIsActive
|
|
||||||
? state.options.technology
|
|
||||||
: kitTokenApiData.technologySelected === 'svg'
|
|
||||||
? 'svg'
|
|
||||||
: 'webfont'
|
|
||||||
)
|
|
||||||
|
|
||||||
const usePro = useSelector(state =>
|
const usePro = useSelector((state) => (kitTokenIsActive ? state.options.usePro : kitTokenApiData.licenseSelected === 'pro'))
|
||||||
kitTokenIsActive
|
|
||||||
? state.options.usePro
|
|
||||||
: kitTokenApiData.licenseSelected === 'pro'
|
|
||||||
)
|
|
||||||
|
|
||||||
const compat = useSelector(state =>
|
const compat = useSelector((state) => (kitTokenIsActive ? state.options.compat : kitTokenApiData.shimEnabled))
|
||||||
kitTokenIsActive
|
|
||||||
? state.options.compat
|
|
||||||
: kitTokenApiData.shimEnabled
|
|
||||||
)
|
|
||||||
|
|
||||||
const version = useSelector(state =>
|
const version = useSelector((state) => (kitTokenIsActive ? state.options.version : kitTokenApiData.version))
|
||||||
kitTokenIsActive
|
|
||||||
? state.options.version
|
|
||||||
: kitTokenApiData.version
|
|
||||||
)
|
|
||||||
|
|
||||||
function getDetectionStatusForOption(option) {
|
function getDetectionStatusForOption(option) {
|
||||||
if (hasChecked && preferenceCheckSuccess && has(pendingOptionConflicts, option)) {
|
if (hasChecked && preferenceCheckSuccess && has(pendingOptionConflicts, option)) {
|
||||||
return <Alert title={ __( 'Preference Conflict', 'font-awesome' ) } type='warning'>
|
return (
|
||||||
{
|
<Alert
|
||||||
size(pendingOptionConflicts[option]) > 1
|
title={__('Preference Conflict', 'font-awesome')}
|
||||||
? <div>
|
type="warning"
|
||||||
|
>
|
||||||
|
{size(pendingOptionConflicts[option]) > 1 ? (
|
||||||
|
<div>
|
||||||
{__('This change might cause problems for these themes or plugins:', 'font-awesome')} {pendingOptionConflicts[option].join(', ')}.
|
{__('This change might cause problems for these themes or plugins:', 'font-awesome')} {pendingOptionConflicts[option].join(', ')}.
|
||||||
</div>
|
</div>
|
||||||
: <div>
|
) : (
|
||||||
|
<div>
|
||||||
{__('This change might cause problems for the theme or plugin:', 'font-awesome')} {pendingOptionConflicts[option][0]}.
|
{__('This change might cause problems for the theme or plugin:', 'font-awesome')} {pendingOptionConflicts[option][0]}.
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (!kitTokenIsActive && !kitTokenApiData)
|
return !kitTokenIsActive && !kitTokenApiData ? (
|
||||||
? <Alert type="warning" title={ __('Oh no! We could not find the kit data for the selected kit token.', 'font-awesome' )}>
|
<Alert
|
||||||
{
|
type="warning"
|
||||||
__( 'Try reloading.', 'font-awesome' )
|
title={__('Oh no! We could not find the kit data for the selected kit token.', 'font-awesome')}
|
||||||
}
|
>
|
||||||
|
{__('Try reloading.', 'font-awesome')}
|
||||||
</Alert>
|
</Alert>
|
||||||
: <div className={ styles['kit-config-view-container'] }>
|
) : (
|
||||||
|
<div className={styles['kit-config-view-container']}>
|
||||||
<table className={styles['selected-kit-settings']}>
|
<table className={styles['selected-kit-settings']}>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -102,18 +89,25 @@ export default function KitConfigView({ kitToken }) {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p className={styles['tip-text']}>
|
<p className={styles['tip-text']}>
|
||||||
{
|
{createInterpolateElement(__('Make changes on <a>fontawesome.com/kits <externalLinkIcon/></a>', 'font-awesome'), {
|
||||||
createInterpolateElement(
|
|
||||||
__( 'Make changes on <a>fontawesome.com/kits <externalLinkIcon/></a>', 'font-awesome' ),
|
|
||||||
{
|
|
||||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||||
a: <a target="_blank" rel="noopener noreferrer" href="https://fontawesome.com/kits" />,
|
a: (
|
||||||
externalLinkIcon: <FontAwesomeIcon icon={faExternalLinkAlt} style={{marginLeft: '.5em'}} />
|
<a
|
||||||
}
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="https://fontawesome.com/kits"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
externalLinkIcon: (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faExternalLinkAlt}
|
||||||
|
style={{ marginLeft: '.5em' }}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
KitConfigView.propTypes = {
|
KitConfigView.propTypes = {
|
||||||
|
|
|
@ -1,47 +1,34 @@
|
||||||
import React, { createRef, useState, useEffect } from 'react'
|
import React, { createRef, useState, useEffect } from 'react'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import Alert from './Alert'
|
import Alert from './Alert'
|
||||||
import {
|
import { resetPendingOptions, queryKits, addPendingOption, checkPreferenceConflicts, updateApiToken, resetOptionsFormState } from './store/actions'
|
||||||
resetPendingOptions,
|
|
||||||
queryKits,
|
|
||||||
addPendingOption,
|
|
||||||
checkPreferenceConflicts,
|
|
||||||
updateApiToken,
|
|
||||||
resetOptionsFormState
|
|
||||||
} from './store/actions'
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {
|
import { faSpinner, faSync, faExternalLinkAlt, faRedo, faSkull, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||||
faSpinner,
|
|
||||||
faSync,
|
|
||||||
faExternalLinkAlt,
|
|
||||||
faRedo,
|
|
||||||
faSkull,
|
|
||||||
faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import { faQuestionCircle, faCheckCircle } from '@fortawesome/free-regular-svg-icons'
|
import { faQuestionCircle, faCheckCircle } from '@fortawesome/free-regular-svg-icons'
|
||||||
import styles from './KitSelectView.module.css'
|
import styles from './KitSelectView.module.css'
|
||||||
import sharedStyles from './App.module.css'
|
import sharedStyles from './App.module.css'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import size from 'lodash/size'
|
import { size } from 'lodash'
|
||||||
import { sprintf, __ } from '@wordpress/i18n'
|
import { sprintf, __ } from '@wordpress/i18n'
|
||||||
|
|
||||||
export default function KitSelectView({ useOption, masterSubmitButtonShowing, setMasterSubmitButtonShowing }) {
|
export default function KitSelectView({ useOption, masterSubmitButtonShowing, setMasterSubmitButtonShowing }) {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const kitTokenActive = useSelector(state => state.options.kitToken)
|
const kitTokenActive = useSelector((state) => state.options.kitToken)
|
||||||
const kitToken = useOption('kitToken')
|
const kitToken = useOption('kitToken')
|
||||||
const [pendingApiToken, setPendingApiToken] = useState(null)
|
const [pendingApiToken, setPendingApiToken] = useState(null)
|
||||||
const [showingRemoveApiTokenAlert, setShowRemoveApiTokenAlert] = useState(false)
|
const [showingRemoveApiTokenAlert, setShowRemoveApiTokenAlert] = useState(false)
|
||||||
const [showApiTokenInputForUpdate, setShowApiTokenInputForUpdate] = useState(false)
|
const [showApiTokenInputForUpdate, setShowApiTokenInputForUpdate] = useState(false)
|
||||||
const apiToken = useSelector(state => {
|
const apiToken = useSelector((state) => {
|
||||||
if (null !== pendingApiToken) return pendingApiToken
|
if (null !== pendingApiToken) return pendingApiToken
|
||||||
|
|
||||||
return state.options.apiToken
|
return state.options.apiToken
|
||||||
})
|
})
|
||||||
const kits = useSelector( state => state.kits ) || []
|
const kits = useSelector((state) => state.kits) || []
|
||||||
const hasSubmitted = useSelector(state => state.optionsFormState.hasSubmitted)
|
const hasSubmitted = useSelector((state) => state.optionsFormState.hasSubmitted)
|
||||||
const submitSuccess = useSelector(state => state.optionsFormState.success)
|
const submitSuccess = useSelector((state) => state.optionsFormState.success)
|
||||||
const submitMessage = useSelector(state => state.optionsFormState.message)
|
const submitMessage = useSelector((state) => state.optionsFormState.message)
|
||||||
const isSubmitting = useSelector(state => state.optionsFormState.isSubmitting)
|
const isSubmitting = useSelector((state) => state.optionsFormState.isSubmitting)
|
||||||
|
|
||||||
function removeApiToken() {
|
function removeApiToken() {
|
||||||
if (!!kitTokenActive) {
|
if (!!kitTokenActive) {
|
||||||
|
@ -66,22 +53,18 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedKit = (kits || []).find(k => k.token === kitToken)
|
const selectedKit = (kits || []).find((k) => k.token === kitToken)
|
||||||
|
|
||||||
if (!selectedKit) {
|
if (!selectedKit) {
|
||||||
throw new Error(
|
throw new Error(sprintf(__('When selecting to use kit %s, somehow the information we needed was missing. Try reloading the page.'), kitToken))
|
||||||
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
|
// We're just resetting back to the state we were in
|
||||||
dispatch(resetPendingOptions())
|
dispatch(resetPendingOptions())
|
||||||
} else {
|
} else {
|
||||||
dispatch(addPendingOption({
|
dispatch(
|
||||||
|
addPendingOption({
|
||||||
kitToken,
|
kitToken,
|
||||||
technology: 'svg' === selectedKit.technologySelected ? 'svg' : 'webfont',
|
technology: 'svg' === selectedKit.technologySelected ? 'svg' : 'webfont',
|
||||||
usePro: 'pro' === selectedKit.licenseSelected,
|
usePro: 'pro' === selectedKit.licenseSelected,
|
||||||
|
@ -90,13 +73,14 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
// At the time this is being implemented, kits don't yet support
|
// At the time this is being implemented, kits don't yet support
|
||||||
// toggling pseudoElement support for SVG, but it's implicitly supported for webfont.
|
// toggling pseudoElement support for SVG, but it's implicitly supported for webfont.
|
||||||
pseudoElements: 'svg' !== selectedKit.technologySelected
|
pseudoElements: 'svg' !== selectedKit.technologySelected
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(checkPreferenceConflicts())
|
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
|
* This seems like a lot of effort just to keep the focus on the API Token input
|
||||||
|
@ -118,8 +102,7 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const hasSavedApiToken = useSelector((state) => !!state.options.apiToken)
|
||||||
const hasSavedApiToken = useSelector(state => !! state.options.apiToken)
|
|
||||||
|
|
||||||
function cancelApiTokenUpdate() {
|
function cancelApiTokenUpdate() {
|
||||||
setShowApiTokenInputForUpdate(false)
|
setShowApiTokenInputForUpdate(false)
|
||||||
|
@ -128,7 +111,6 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
}
|
}
|
||||||
|
|
||||||
function ApiTokenInput() {
|
function ApiTokenInput() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (submitSuccess && showApiTokenInputForUpdate) {
|
if (submitSuccess && showApiTokenInputForUpdate) {
|
||||||
setShowApiTokenInputForUpdate(false)
|
setShowApiTokenInputForUpdate(false)
|
||||||
|
@ -136,10 +118,15 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return <>
|
return (
|
||||||
|
<>
|
||||||
<div className={classnames(styles['field-apitoken'], { [styles['api-token-update']]: showApiTokenInputForUpdate })}>
|
<div className={classnames(styles['field-apitoken'], { [styles['api-token-update']]: showApiTokenInputForUpdate })}>
|
||||||
<label htmlFor="api_token">
|
<label htmlFor="api_token">
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faQuestionCircle } size="lg" />
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faQuestionCircle}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
{__('API Token', 'font-awesome')}
|
{__('API Token', 'font-awesome')}
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
|
@ -150,15 +137,27 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
ref={apiTokenInputRef}
|
ref={apiTokenInputRef}
|
||||||
value={pendingApiToken || ''}
|
value={pendingApiToken || ''}
|
||||||
size="20"
|
size="20"
|
||||||
onChange={ e => {
|
onChange={(e) => {
|
||||||
setApiTokenInputHasFocus(true)
|
setApiTokenInputHasFocus(true)
|
||||||
setPendingApiToken(e.target.value)
|
setPendingApiToken(e.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<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'}} />
|
'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>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -174,32 +173,39 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
onMouseDown={() => {
|
onMouseDown={() => {
|
||||||
dispatch(updateApiToken({ apiToken: pendingApiToken, runQueryKits: true }))
|
dispatch(updateApiToken({ apiToken: pendingApiToken, runQueryKits: true }))
|
||||||
setPendingApiToken(null)
|
setPendingApiToken(null)
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{
|
{hasSubmitted && !submitSuccess && (
|
||||||
(hasSubmitted && ! submitSuccess) &&
|
|
||||||
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
||||||
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSkull } />
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faSkull}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={ sharedStyles['explanation'] }>
|
<div className={sharedStyles['explanation']}>{submitMessage}</div>
|
||||||
{ submitMessage }
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
}
|
{isSubmitting && (
|
||||||
{
|
|
||||||
isSubmitting &&
|
|
||||||
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={faSpinner} spin/>
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faSpinner}
|
||||||
|
spin
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
{
|
{showApiTokenInputForUpdate && !isSubmitting && (
|
||||||
(showApiTokenInputForUpdate && ! isSubmitting) &&
|
<button
|
||||||
<button onClick={ () => cancelApiTokenUpdate() } className={ styles['button-dismissable'] }>{ __('Nevermind', 'font-awesome') }</button>
|
onClick={() => cancelApiTokenUpdate()}
|
||||||
}
|
className={styles['button-dismissable']}
|
||||||
|
>
|
||||||
|
{__('Nevermind', 'font-awesome')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ApiTokenControl() {
|
function ApiTokenControl() {
|
||||||
|
@ -210,40 +216,67 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
setShowRemoveApiTokenAlert(false)
|
setShowRemoveApiTokenAlert(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={ styles['api-token-control-wrapper'] }>
|
return (
|
||||||
|
<div className={styles['api-token-control-wrapper']}>
|
||||||
<div className={classnames(styles['api-token-control'], { [styles['api-token-update']]: showApiTokenInputForUpdate })}>
|
<div className={classnames(styles['api-token-control'], { [styles['api-token-update']]: showApiTokenInputForUpdate })}>
|
||||||
{
|
{showApiTokenInputForUpdate ? (
|
||||||
showApiTokenInputForUpdate
|
<ApiTokenInput />
|
||||||
? <ApiTokenInput />
|
) : (
|
||||||
: <>
|
<>
|
||||||
<p className={styles['token-saved']}>
|
<p className={styles['token-saved']}>
|
||||||
<span>
|
<span>
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faCheckCircle } size="lg" />
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faCheckCircle}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
{__('API Token Saved', 'font-awesome')}
|
{__('API Token Saved', 'font-awesome')}
|
||||||
</p>
|
</p>
|
||||||
{
|
{!!apiToken && (
|
||||||
!!apiToken &&
|
|
||||||
<div className={styles['button-group']}>
|
<div className={styles['button-group']}>
|
||||||
<button onClick={ () => switchToApiTokenUpdate() } className={ styles['refresh'] } type="button">
|
<button
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSync } title="update" alt="update" />
|
onClick={() => switchToApiTokenUpdate()}
|
||||||
|
className={styles['refresh']}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faSync}
|
||||||
|
title="update"
|
||||||
|
alt="update"
|
||||||
|
/>
|
||||||
<span>{__('Update token', 'font-awesome')}</span>
|
<span>{__('Update token', 'font-awesome')}</span>
|
||||||
</button>
|
</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>
|
||||||
}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{
|
{showingRemoveApiTokenAlert && (
|
||||||
showingRemoveApiTokenAlert &&
|
|
||||||
<div className={styles['api-token-control-alert-wrapper']}>
|
<div className={styles['api-token-control-alert-wrapper']}>
|
||||||
<Alert title={ __( 'Whoa, whoa, whoa!', 'font-awesome' ) } type='warning'>
|
<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')}
|
{__('You can\'t remove your API token when "Use a Kit" is active. Switch to "Use CDN" first.', 'font-awesome')}
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS = {
|
const STATUS = {
|
||||||
|
@ -257,8 +290,7 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
}
|
}
|
||||||
|
|
||||||
function KitSelector() {
|
function KitSelector() {
|
||||||
const status =
|
const status = apiToken
|
||||||
apiToken
|
|
||||||
? kitsQueryStatus.isSubmitting
|
? kitsQueryStatus.isSubmitting
|
||||||
? STATUS.querying
|
? STATUS.querying
|
||||||
: kitsQueryStatus.hasSubmitted
|
: kitsQueryStatus.hasSubmitted
|
||||||
|
@ -272,125 +304,164 @@ export default function KitSelectView({ useOption, masterSubmitButtonShowing, se
|
||||||
: STATUS.apiTokenReadyNoKitsYet
|
: STATUS.apiTokenReadyNoKitsYet
|
||||||
: STATUS.noApiToken
|
: STATUS.noApiToken
|
||||||
|
|
||||||
const kitRefreshButton = <button onClick={ () => dispatch(queryKits()) } className={ styles['refresh'] }>
|
const kitRefreshButton = (
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faRedo } title="refresh" alt="refresh" />
|
<button
|
||||||
<span>
|
onClick={() => dispatch(queryKits())}
|
||||||
{
|
className={styles['refresh']}
|
||||||
0 === size(kits)
|
>
|
||||||
? __( 'Get latest kits data', 'font-awesome' )
|
<FontAwesomeIcon
|
||||||
: __( 'Refresh kits data', 'font-awesome' )
|
className={sharedStyles['icon']}
|
||||||
}
|
icon={faRedo}
|
||||||
</span>
|
title="refresh"
|
||||||
|
alt="refresh"
|
||||||
|
/>
|
||||||
|
<span>{0 === size(kits) ? __('Get latest kits data', 'font-awesome') : __('Refresh kits data', 'font-awesome')}</span>
|
||||||
</button>
|
</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'] }>
|
return (
|
||||||
|
<div className={styles['kit-selector-container']}>
|
||||||
{activeKitNotice}
|
{activeKitNotice}
|
||||||
|
|
||||||
<div className={styles['wrap-selectkit']}>
|
<div className={styles['wrap-selectkit']}>
|
||||||
<h3 className={ styles['title-selectkit'] }><FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faQuestionCircle } size="lg" />
|
<h3 className={styles['title-selectkit']}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faQuestionCircle}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
{__('Pick a Kit to Use or Check Settings', 'font-awesome')}
|
{__('Pick a Kit to Use or Check Settings', 'font-awesome')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className={styles['selectkit']}>
|
<div className={styles['selectkit']}>
|
||||||
<p>
|
<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>
|
</p>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
noApiToken: 'noApiToken',
|
noApiToken: 'noApiToken',
|
||||||
apiTokenReadyNoKitsYet: <>{ activeKitNotice } { kitRefreshButton }</>,
|
apiTokenReadyNoKitsYet: (
|
||||||
querying:
|
<>
|
||||||
|
{activeKitNotice} {kitRefreshButton}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
querying: (
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>{__('Loading your kits...', 'font-awesome')}</span>
|
||||||
{ __( 'Loading your kits...', 'font-awesome' ) }
|
|
||||||
</span>
|
|
||||||
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={faSpinner} spin/>
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faSpinner}
|
||||||
|
spin
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>,
|
</div>
|
||||||
|
),
|
||||||
|
|
||||||
networkError:
|
networkError: (
|
||||||
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
||||||
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSkull } />
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faSkull}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={ sharedStyles['explanation'] }>
|
<div className={sharedStyles['explanation']}>{kitsQueryStatus.message}</div>
|
||||||
{ kitsQueryStatus.message }
|
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
),
|
||||||
|
|
||||||
noKitsFoundAfterQuery:
|
noKitsFoundAfterQuery: (
|
||||||
<>
|
<>
|
||||||
<Alert title="Zoinks! Looks like you don't have any kits set up yet." type="info">
|
<Alert
|
||||||
|
title="Zoinks! Looks like you don't have any kits set up yet."
|
||||||
|
type="info"
|
||||||
|
>
|
||||||
<p>
|
<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">
|
{__('Head over to Font Awesome to create one, then come back here and refresh your kits.', 'font-awesome')}{' '}
|
||||||
{ __( 'Create a kit on Font Awesome', 'font-awesome' ) } <FontAwesomeIcon icon={faExternalLinkAlt} /></a>
|
<a
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
href="https://fontawesome.com/kits"
|
||||||
|
>
|
||||||
|
{__('Create a kit on Font Awesome', 'font-awesome')} <FontAwesomeIcon icon={faExternalLinkAlt} />
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
{kitRefreshButton}
|
{kitRefreshButton}
|
||||||
</>,
|
</>
|
||||||
|
),
|
||||||
|
|
||||||
kitSelection:
|
kitSelection: (
|
||||||
<>
|
<>
|
||||||
<div className={styles['field-kitselect']}>
|
<div className={styles['field-kitselect']}>
|
||||||
<select
|
<select
|
||||||
className={styles['kit-select']}
|
className={styles['kit-select']}
|
||||||
id="kits"
|
id="kits"
|
||||||
name="kit"
|
name="kit"
|
||||||
onChange={ e => handleKitChange({ kitToken: e.target.value }) }
|
onChange={(e) => handleKitChange({ kitToken: e.target.value })}
|
||||||
disabled={!masterSubmitButtonShowing}
|
disabled={!masterSubmitButtonShowing}
|
||||||
value={kitToken || ''}
|
value={kitToken || ''}
|
||||||
>
|
>
|
||||||
<option key='empty' value=''>{ __( 'Select a kit', 'font-awesome' ) }</option>
|
<option
|
||||||
{
|
key="empty"
|
||||||
kits.map((kit, index) => {
|
value=""
|
||||||
return <option key={ index } value={ kit.token }>
|
>
|
||||||
|
{__('Select a kit', 'font-awesome')}
|
||||||
|
</option>
|
||||||
|
{kits.map((kit, index) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
key={index}
|
||||||
|
value={kit.token}
|
||||||
|
>
|
||||||
{`${kit.name} (${kit.token})`}
|
{`${kit.name} (${kit.token})`}
|
||||||
</option>
|
</option>
|
||||||
})
|
)
|
||||||
}
|
})}
|
||||||
</select>
|
</select>
|
||||||
{kitRefreshButton}
|
{kitRefreshButton}
|
||||||
</div>
|
</div>
|
||||||
</>,
|
|
||||||
|
|
||||||
showingOnlyActiveKit:
|
|
||||||
<>
|
|
||||||
{ kitRefreshButton }
|
|
||||||
</>
|
</>
|
||||||
|
),
|
||||||
|
|
||||||
|
showingOnlyActiveKit: <>{kitRefreshButton}</>
|
||||||
}[status]
|
}[status]
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return (
|
||||||
|
<div>
|
||||||
<div className={styles['kit-tab-content']}>
|
<div className={styles['kit-tab-content']}>
|
||||||
{
|
{hasSavedApiToken ? (
|
||||||
hasSavedApiToken
|
<>
|
||||||
? <>
|
|
||||||
<ApiTokenControl />
|
<ApiTokenControl />
|
||||||
<KitSelector />
|
<KitSelector />
|
||||||
</>
|
</>
|
||||||
: <ApiTokenInput />
|
) : (
|
||||||
}
|
<ApiTokenInput />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
KitSelectView.propTypes = {
|
KitSelectView.propTypes = {
|
||||||
|
|
|
@ -7,18 +7,21 @@ import { __ } from '@wordpress/i18n'
|
||||||
import createInterpolateElement from './createInterpolateElement'
|
import createInterpolateElement from './createInterpolateElement'
|
||||||
|
|
||||||
export default function ManageFontAwesomeVersionsSection() {
|
export default function ManageFontAwesomeVersionsSection() {
|
||||||
return <div className={ classnames(sharedStyles['explanation'], styles['font-awesome-versions-section']) }>
|
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>
|
<h2 className={sharedStyles['section-title']}>{__('Versions of Font Awesome Active on Your Site', 'font-awesome')}</h2>
|
||||||
<p>
|
<p>
|
||||||
{
|
{createInterpolateElement(
|
||||||
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>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 />
|
b: <b />
|
||||||
}
|
}
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</p>
|
</p>
|
||||||
<ClientPreferencesView />
|
<ClientPreferencesView />
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,40 +6,30 @@ import KitConfigView from './KitConfigView'
|
||||||
import sharedStyles from './App.module.css'
|
import sharedStyles from './App.module.css'
|
||||||
import optionStyles from './CdnConfigView.module.css'
|
import optionStyles from './CdnConfigView.module.css'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {
|
import { faDotCircle, faSpinner, faCheck, faSkull } from '@fortawesome/free-solid-svg-icons'
|
||||||
faDotCircle,
|
|
||||||
faSpinner,
|
|
||||||
faCheck,
|
|
||||||
faSkull,
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import { faCircle } from '@fortawesome/free-regular-svg-icons'
|
import { faCircle } from '@fortawesome/free-regular-svg-icons'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import styles from './SettingsTab.module.css'
|
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 { addPendingOption, submitPendingOptions, chooseAwayFromKitConfig, chooseIntoKitConfig } from './store/actions'
|
||||||
import CheckingOptionStatusIndicator from './CheckingOptionsStatusIndicator'
|
import CheckingOptionStatusIndicator from './CheckingOptionsStatusIndicator'
|
||||||
import size from 'lodash/size'
|
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
|
|
||||||
export default function SettingsTab() {
|
export default function SettingsTab() {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const alreadyUsingKit = useSelector( state => !!state.options.kitToken )
|
const alreadyUsingKit = useSelector((state) => !!state.options.kitToken)
|
||||||
const [useKit, setUseKit] = useState(alreadyUsingKit)
|
const [useKit, setUseKit] = useState(alreadyUsingKit)
|
||||||
const isChecking = useSelector(state => state.preferenceConflictDetection.isChecking)
|
const isChecking = useSelector((state) => state.preferenceConflictDetection.isChecking)
|
||||||
const hasSubmitted = useSelector(state => state.optionsFormState.hasSubmitted)
|
const hasSubmitted = useSelector((state) => state.optionsFormState.hasSubmitted)
|
||||||
const submitSuccess = useSelector(state => state.optionsFormState.success)
|
const submitSuccess = useSelector((state) => state.optionsFormState.success)
|
||||||
const submitMessage = useSelector(state => state.optionsFormState.message)
|
const submitMessage = useSelector((state) => state.optionsFormState.message)
|
||||||
const isSubmitting = useSelector(state => state.optionsFormState.isSubmitting)
|
const isSubmitting = useSelector((state) => state.optionsFormState.isSubmitting)
|
||||||
const pendingOptions = useSelector(state => state.pendingOptions)
|
const pendingOptions = useSelector((state) => state.pendingOptions)
|
||||||
const apiToken = useSelector(state => state.options.apiToken)
|
const apiToken = useSelector((state) => state.options.apiToken)
|
||||||
const [masterSubmitButtonShowing, setMasterSubmitButtonShowing] = useState(true)
|
const [masterSubmitButtonShowing, setMasterSubmitButtonShowing] = useState(true)
|
||||||
|
|
||||||
function useOption(option) {
|
function useOption(option) {
|
||||||
return useSelector(state =>
|
return useSelector((state) => (has(state.pendingOptions, option) ? state.pendingOptions[option] : state.options[option]))
|
||||||
has(state.pendingOptions, option)
|
|
||||||
? state.pendingOptions[option]
|
|
||||||
: state.options[option]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit(e) {
|
function handleSubmit(e) {
|
||||||
|
@ -54,7 +44,7 @@ export default function SettingsTab() {
|
||||||
const kitToken = useOption('kitToken')
|
const kitToken = useOption('kitToken')
|
||||||
|
|
||||||
// The one that's actually saved in the database already
|
// 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 = {}) {
|
function handleOptionChange(change = {}) {
|
||||||
dispatch(addPendingOption(change))
|
dispatch(addPendingOption(change))
|
||||||
|
@ -78,7 +68,9 @@ export default function SettingsTab() {
|
||||||
dispatch(chooseIntoKitConfig())
|
dispatch(chooseIntoKitConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div><div className={ sharedStyles['wrapper-div'] }>
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={sharedStyles['wrapper-div']}>
|
||||||
<h3>{__('How are you using Font Awesome?', 'font-awesome')}</h3>
|
<h3>{__('How are you using Font Awesome?', 'font-awesome')}</h3>
|
||||||
<div className={styles['select-config-container']}>
|
<div className={styles['select-config-container']}>
|
||||||
<span>
|
<span>
|
||||||
|
@ -91,7 +83,10 @@ export default function SettingsTab() {
|
||||||
onChange={() => handleSwitchToKitConfig()}
|
onChange={() => handleSwitchToKitConfig()}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="select_use_kits" className={ optionStyles['option-label'] }>
|
<label
|
||||||
|
htmlFor="select_use_kits"
|
||||||
|
className={optionStyles['option-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faDotCircle}
|
icon={faDotCircle}
|
||||||
|
@ -106,9 +101,7 @@ export default function SettingsTab() {
|
||||||
fixedWidth
|
fixedWidth
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className={ optionStyles['option-label-text'] }>
|
<span className={optionStyles['option-label-text']}>{__('Use A Kit', 'font-awesome')}</span>
|
||||||
{ __( 'Use A Kit', 'font-awesome' ) }
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
|
@ -121,7 +114,10 @@ export default function SettingsTab() {
|
||||||
onChange={() => handleSwitchAwayFromKitConfig()}
|
onChange={() => handleSwitchAwayFromKitConfig()}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-radio-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="select_use_cdn" className={ optionStyles['option-label'] }>
|
<label
|
||||||
|
htmlFor="select_use_cdn"
|
||||||
|
className={optionStyles['option-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faDotCircle}
|
icon={faDotCircle}
|
||||||
|
@ -136,25 +132,32 @@ export default function SettingsTab() {
|
||||||
fixedWidth
|
fixedWidth
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className={ optionStyles['option-label-text'] }>
|
<span className={optionStyles['option-label-text']}>{__('Use CDN', 'font-awesome')}</span>
|
||||||
{ __( 'Use CDN', 'font-awesome' ) }
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<>
|
<>
|
||||||
{
|
{useKit ? (
|
||||||
useKit
|
<>
|
||||||
? <>
|
<KitSelectView
|
||||||
<KitSelectView useOption={ useOption } handleOptionChange={ handleOptionChange } handleSubmit={ handleSubmit } masterSubmitButtonShowing={ masterSubmitButtonShowing } setMasterSubmitButtonShowing={ setMasterSubmitButtonShowing }/>
|
useOption={useOption}
|
||||||
|
handleOptionChange={handleOptionChange}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
masterSubmitButtonShowing={masterSubmitButtonShowing}
|
||||||
|
setMasterSubmitButtonShowing={setMasterSubmitButtonShowing}
|
||||||
|
/>
|
||||||
{!!kitToken && <KitConfigView kitToken={kitToken} />}
|
{!!kitToken && <KitConfigView kitToken={kitToken} />}
|
||||||
</>
|
</>
|
||||||
: <CdnConfigView useOption={ useOption } handleOptionChange={ handleOptionChange } handleSubmit={ handleSubmit }/>
|
) : (
|
||||||
}
|
<CdnConfigView
|
||||||
|
useOption={useOption}
|
||||||
|
handleOptionChange={handleOptionChange}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
{
|
{(!useKit || (apiToken && masterSubmitButtonShowing)) && (
|
||||||
(!useKit || ( apiToken && masterSubmitButtonShowing ) ) &&
|
|
||||||
<div className={classnames(sharedStyles['submit-wrapper'], ['submit'])}>
|
<div className={classnames(sharedStyles['submit-wrapper'], ['submit'])}>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -165,33 +168,41 @@ export default function SettingsTab() {
|
||||||
disabled={size(pendingOptions) === 0}
|
disabled={size(pendingOptions) === 0}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
{ hasSubmitted
|
{hasSubmitted ? (
|
||||||
? submitSuccess
|
submitSuccess ? (
|
||||||
? <span className={ classnames(sharedStyles['submit-status'], sharedStyles['success']) }>
|
<span className={classnames(sharedStyles['submit-status'], sharedStyles['success'])}>
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faCheck } />
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faCheck}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
: <div className={ classnames(sharedStyles['submit-status'], sharedStyles['fail']) }>
|
) : (
|
||||||
|
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
||||||
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSkull } />
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faSkull}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={ sharedStyles['explanation'] }>
|
<div className={sharedStyles['explanation']}>{submitMessage}</div>
|
||||||
{ submitMessage }
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
: null
|
) : null}
|
||||||
}
|
{isSubmitting ? (
|
||||||
{
|
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
||||||
isSubmitting
|
<FontAwesomeIcon
|
||||||
? <span className={ classnames(sharedStyles['submit-status'], sharedStyles['submitting']) }>
|
className={sharedStyles['icon']}
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={faSpinner} spin/>
|
icon={faSpinner}
|
||||||
|
spin
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
: isChecking
|
) : isChecking ? (
|
||||||
? <CheckingOptionStatusIndicator/>
|
<CheckingOptionStatusIndicator />
|
||||||
: size(pendingOptions) > 0
|
) : size(pendingOptions) > 0 ? (
|
||||||
? <span className={ sharedStyles['submit-status'] }>{ __( 'you have pending changes', 'font-awesome' ) }</span>
|
<span className={sharedStyles['submit-status']}>{__('you have pending changes', 'font-awesome')}</span>
|
||||||
: null
|
) : null}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ManageFontAwesomeVersionsSection from './ManageFontAwesomeVersionsSection'
|
import ManageFontAwesomeVersionsSection from './ManageFontAwesomeVersionsSection'
|
||||||
import UnregisteredClientsView from './UnregisteredClientsView'
|
import UnregisteredClientsView from './UnregisteredClientsView'
|
||||||
import V3DeprecationWarning from './V3DeprecationWarning'
|
|
||||||
import ConflictDetectionScannerSection from './ConflictDetectionScannerSection'
|
import ConflictDetectionScannerSection from './ConflictDetectionScannerSection'
|
||||||
import sharedStyles from './App.module.css'
|
import sharedStyles from './App.module.css'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
@ -12,21 +11,17 @@ import {
|
||||||
resetPendingBlocklistSubmissionStatus,
|
resetPendingBlocklistSubmissionStatus,
|
||||||
resetUnregisteredClientsDeletionStatus
|
resetUnregisteredClientsDeletionStatus
|
||||||
} from './store/actions'
|
} from './store/actions'
|
||||||
import {
|
import { faCheck, faSkull, faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||||
faCheck,
|
|
||||||
faSkull,
|
|
||||||
faSpinner } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import size from 'lodash/size'
|
import { size } from 'lodash'
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
|
|
||||||
export default function TroubleshootTab() {
|
export default function TroubleshootTab() {
|
||||||
const dispatch = useDispatch()
|
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 blocklistUpdateStatus = useSelector((state) => state.blocklistUpdateStatus)
|
||||||
const unregisteredClientsDeletionStatus = useSelector(state => state.unregisteredClientsDeletionStatus)
|
const unregisteredClientsDeletionStatus = useSelector((state) => state.unregisteredClientsDeletionStatus)
|
||||||
|
|
||||||
const showSubmitButton = size(unregisteredClients) > 0
|
const showSubmitButton = size(unregisteredClients) > 0
|
||||||
const hasPendingChanges = null !== blocklistUpdateStatus.pending || size(unregisteredClientsDeletionStatus.pending) > 0
|
const hasPendingChanges = null !== blocklistUpdateStatus.pending || size(unregisteredClientsDeletionStatus.pending) > 0
|
||||||
|
@ -54,15 +49,14 @@ export default function TroubleshootTab() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return (
|
||||||
|
<>
|
||||||
<div className={sharedStyles['wrapper-div']}>
|
<div className={sharedStyles['wrapper-div']}>
|
||||||
{ hasV3DeprecationWarning && <V3DeprecationWarning /> }
|
|
||||||
<ConflictDetectionScannerSection />
|
<ConflictDetectionScannerSection />
|
||||||
<ManageFontAwesomeVersionsSection />
|
<ManageFontAwesomeVersionsSection />
|
||||||
<UnregisteredClientsView />
|
<UnregisteredClientsView />
|
||||||
</div>
|
</div>
|
||||||
{
|
{showSubmitButton && (
|
||||||
showSubmitButton &&
|
|
||||||
<div className={classnames(sharedStyles['submit-wrapper'], ['submit'])}>
|
<div className={classnames(sharedStyles['submit-wrapper'], ['submit'])}>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -73,36 +67,42 @@ export default function TroubleshootTab() {
|
||||||
disabled={!hasPendingChanges}
|
disabled={!hasPendingChanges}
|
||||||
onClick={handleSubmitClick}
|
onClick={handleSubmitClick}
|
||||||
/>
|
/>
|
||||||
{ hasSubmitted
|
{hasSubmitted ? (
|
||||||
? submitSuccess
|
submitSuccess ? (
|
||||||
? <span className={ classnames(sharedStyles['submit-status'], sharedStyles['success']) }>
|
<span className={classnames(sharedStyles['submit-status'], sharedStyles['success'])}>
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faCheck } />
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faCheck}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
: <div className={ classnames(sharedStyles['submit-status'], sharedStyles['fail']) }>
|
) : (
|
||||||
|
<div className={classnames(sharedStyles['submit-status'], sharedStyles['fail'])}>
|
||||||
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
<div className={classnames(sharedStyles['fail-icon-container'])}>
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={ faSkull } />
|
<FontAwesomeIcon
|
||||||
|
className={sharedStyles['icon']}
|
||||||
|
icon={faSkull}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={sharedStyles['explanation']}>
|
<div className={sharedStyles['explanation']}>
|
||||||
{
|
{!!blocklistUpdateStatus.message && <p> {blocklistUpdateStatus.message} </p>}
|
||||||
!!blocklistUpdateStatus.message && <p> { blocklistUpdateStatus.message } </p>
|
{!!unregisteredClientsDeletionStatus.message && <p> {unregisteredClientsDeletionStatus.message} </p>}
|
||||||
}
|
|
||||||
{
|
|
||||||
!!unregisteredClientsDeletionStatus.message && <p> { unregisteredClientsDeletionStatus.message } </p>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: null
|
)
|
||||||
}
|
) : null}
|
||||||
{
|
{isSubmitting ? (
|
||||||
isSubmitting
|
<span className={classnames(sharedStyles['submit-status'], sharedStyles['submitting'])}>
|
||||||
? <span className={ classnames(sharedStyles['submit-status'], sharedStyles['submitting']) }>
|
<FontAwesomeIcon
|
||||||
<FontAwesomeIcon className={ sharedStyles['icon'] } icon={faSpinner} spin/>
|
className={sharedStyles['icon']}
|
||||||
|
icon={faSpinner}
|
||||||
|
spin
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
: hasPendingChanges
|
) : hasPendingChanges ? (
|
||||||
? <span className={ sharedStyles['submit-status'] }>{ __( 'you have pending changes', 'font-awesome' ) }</span>
|
<span className={sharedStyles['submit-status']}>{__('you have pending changes', 'font-awesome')}</span>
|
||||||
: null
|
) : null}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</>
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,14 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import {
|
import { updatePendingBlocklist, updatePendingUnregisteredClientsForDeletion } from './store/actions'
|
||||||
updatePendingBlocklist,
|
|
||||||
updatePendingUnregisteredClientsForDeletion
|
|
||||||
} from './store/actions'
|
|
||||||
import { blocklistSelector } from './store/reducers'
|
import { blocklistSelector } from './store/reducers'
|
||||||
import styles from './UnregisteredClientsView.module.css'
|
import styles from './UnregisteredClientsView.module.css'
|
||||||
import sharedStyles from './App.module.css'
|
import sharedStyles from './App.module.css'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {
|
import { faCheckSquare, faThumbsUp } from '@fortawesome/free-solid-svg-icons'
|
||||||
faCheckSquare,
|
import { faSquare } from '@fortawesome/free-regular-svg-icons'
|
||||||
faThumbsUp } from '@fortawesome/free-solid-svg-icons'
|
import { get, truncate, size, isEqual, sortedUniq, difference } from 'lodash'
|
||||||
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 { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
import createInterpolateElement from './createInterpolateElement'
|
import createInterpolateElement from './createInterpolateElement'
|
||||||
|
|
||||||
|
@ -33,93 +22,93 @@ function excerpt( content ) {
|
||||||
|
|
||||||
export default function UnregisteredClientsView() {
|
export default function UnregisteredClientsView() {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const unregisteredClients = useSelector(state => state.unregisteredClients)
|
const unregisteredClients = useSelector((state) => state.unregisteredClients)
|
||||||
const savedBlocklist = useSelector(state => blocklistSelector(state))
|
const savedBlocklist = useSelector((state) => blocklistSelector(state))
|
||||||
const blocklist = useSelector(state => {
|
const blocklist = useSelector((state) => {
|
||||||
if (null !== state.blocklistUpdateStatus.pending) {
|
if (null !== state.blocklistUpdateStatus.pending) {
|
||||||
return state.blocklistUpdateStatus.pending
|
return state.blocklistUpdateStatus.pending
|
||||||
} else {
|
} else {
|
||||||
return savedBlocklist
|
return savedBlocklist
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const deleteList = useSelector( state => state.unregisteredClientsDeletionStatus.pending)
|
const deleteList = useSelector((state) => state.unregisteredClientsDeletionStatus.pending)
|
||||||
const detectedUnregisteredClients = size(Object.keys(unregisteredClients)) > 0
|
const detectedUnregisteredClients = size(Object.keys(unregisteredClients)) > 0
|
||||||
const allDetectedConflictsSelectedForBlocking =
|
const allDetectedConflictsSelectedForBlocking = isEqual(Object.keys(unregisteredClients).sort(), [...(blocklist || [])].sort())
|
||||||
isEqual(Object.keys(unregisteredClients).sort(), [...(blocklist || [])].sort())
|
const allDetectedConflictsSelectedForRemoval = isEqual(Object.keys(unregisteredClients).sort(), [...(deleteList || [])].sort())
|
||||||
const allDetectedConflictsSelectedForRemoval =
|
|
||||||
isEqual(Object.keys(unregisteredClients).sort(), [...(deleteList || [])].sort())
|
|
||||||
const allDetectedConflicts = Object.keys(unregisteredClients)
|
const allDetectedConflicts = Object.keys(unregisteredClients)
|
||||||
|
|
||||||
function isCheckedForBlocking(md5) {
|
function isCheckedForBlocking(md5) {
|
||||||
return !! blocklist.find(x => x === md5)
|
return !!blocklist.find((x) => x === md5)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCheckedForRemoval(md5) {
|
function isCheckedForRemoval(md5) {
|
||||||
return !! deleteList.find(x => x === md5)
|
return !!deleteList.find((x) => x === md5)
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeCheckForRemoval(md5, allDetectedConflicts) {
|
function changeCheckForRemoval(md5, allDetectedConflicts) {
|
||||||
const newDeleteList = 'all' === md5
|
const newDeleteList =
|
||||||
|
'all' === md5
|
||||||
? allDetectedConflictsSelectedForRemoval
|
? allDetectedConflictsSelectedForRemoval
|
||||||
? [] // uncheck them all
|
? [] // uncheck them all
|
||||||
: allDetectedConflicts // check them all
|
: allDetectedConflicts // check them all
|
||||||
: isCheckedForRemoval(md5)
|
: isCheckedForRemoval(md5)
|
||||||
? deleteList.filter(x => x !== md5)
|
? deleteList.filter((x) => x !== md5)
|
||||||
: [...deleteList, md5]
|
: [...deleteList, md5]
|
||||||
|
|
||||||
dispatch(updatePendingUnregisteredClientsForDeletion(newDeleteList))
|
dispatch(updatePendingUnregisteredClientsForDeletion(newDeleteList))
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeCheckForBlocking(md5, allDetectedConflicts) {
|
function changeCheckForBlocking(md5, allDetectedConflicts) {
|
||||||
const newBlocklist = 'all' === md5
|
const newBlocklist =
|
||||||
|
'all' === md5
|
||||||
? allDetectedConflictsSelectedForBlocking
|
? allDetectedConflictsSelectedForBlocking
|
||||||
? [] // uncheck them all
|
? [] // uncheck them all
|
||||||
: allDetectedConflicts // check them all
|
: allDetectedConflicts // check them all
|
||||||
: isCheckedForBlocking(md5)
|
: isCheckedForBlocking(md5)
|
||||||
? blocklist.filter(x => x !== md5)
|
? blocklist.filter((x) => x !== md5)
|
||||||
: [...blocklist, md5]
|
: [...blocklist, md5]
|
||||||
|
|
||||||
const orig = sortedUnique( savedBlocklist )
|
const orig = sortedUniq(savedBlocklist)
|
||||||
const updated = sortedUnique( newBlocklist )
|
const updated = sortedUniq(newBlocklist)
|
||||||
|
|
||||||
if(
|
if (orig.length === updated.length && 0 === size(difference(orig, updated)) && 0 === size(difference(updated, orig))) {
|
||||||
orig.length === updated.length &&
|
|
||||||
0 === size( difference(orig, updated) ) &&
|
|
||||||
0 === size( difference(updated, orig) )
|
|
||||||
) {
|
|
||||||
dispatch(updatePendingBlocklist(null))
|
dispatch(updatePendingBlocklist(null))
|
||||||
} else {
|
} else {
|
||||||
dispatch(updatePendingBlocklist(newBlocklist))
|
dispatch(updatePendingBlocklist(newBlocklist))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={ classnames(styles['unregistered-clients'], { [styles['none-detected']]: !detectedUnregisteredClients }) }>
|
return (
|
||||||
|
<div className={classnames(styles['unregistered-clients'], { [styles['none-detected']]: !detectedUnregisteredClients })}>
|
||||||
<h3 className={sharedStyles['section-title']}>{__('Other themes or plugins', 'font-awesome')}</h3>
|
<h3 className={sharedStyles['section-title']}>{__('Other themes or plugins', 'font-awesome')}</h3>
|
||||||
{detectedUnregisteredClients
|
{detectedUnregisteredClients ? (
|
||||||
? <div>
|
<div>
|
||||||
<p className={sharedStyles['explanation']}>
|
<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>
|
</p>
|
||||||
<table className={classnames('widefat', 'striped')}>
|
<table className={classnames('widefat', 'striped')}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className={sharedStyles['table-header']}>
|
<tr className={sharedStyles['table-header']}>
|
||||||
<th>
|
<th>
|
||||||
<div className={styles['column-label']}>{__('Block', 'font-awesome')}</div>
|
<div className={styles['column-label']}>{__('Block', 'font-awesome')}</div>
|
||||||
{
|
{size(allDetectedConflicts) > 1 && (
|
||||||
size( allDetectedConflicts ) > 1 &&
|
|
||||||
<div className={styles['block-all-container']}>
|
<div className={styles['block-all-container']}>
|
||||||
<input
|
<input
|
||||||
id='block_all_detected_conflicts'
|
id="block_all_detected_conflicts"
|
||||||
name='block_all_detected_conflicts'
|
name="block_all_detected_conflicts"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value='all'
|
value="all"
|
||||||
checked={allDetectedConflictsSelectedForBlocking}
|
checked={allDetectedConflictsSelectedForBlocking}
|
||||||
onChange={() => changeCheckForBlocking('all', allDetectedConflicts)}
|
onChange={() => changeCheckForBlocking('all', allDetectedConflicts)}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor='block_all_detected_conflicts' className={ styles['checkbox-label'] }>
|
<label
|
||||||
|
htmlFor="block_all_detected_conflicts"
|
||||||
|
className={styles['checkbox-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faCheckSquare}
|
icon={faCheckSquare}
|
||||||
|
@ -137,33 +126,31 @@ export default function UnregisteredClientsView() {
|
||||||
{__('All', 'font-awesome')}
|
{__('All', 'font-awesome')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<span className={ styles['column-label'] }>
|
<span className={styles['column-label']}>{__('Type', 'font-awesome')}</span>
|
||||||
{ __( 'Type', 'font-awesome' ) }
|
|
||||||
</span>
|
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<span className={ styles['column-label'] }>
|
<span className={styles['column-label']}>{__('URL', 'font-awesome')}</span>
|
||||||
{ __( 'URL', 'font-awesome' ) }
|
|
||||||
</span>
|
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<div className={styles['column-label']}>{__('Clear', 'font-awesome')}</div>
|
<div className={styles['column-label']}>{__('Clear', 'font-awesome')}</div>
|
||||||
{
|
{size(allDetectedConflicts) > 1 && (
|
||||||
size( allDetectedConflicts ) > 1 &&
|
|
||||||
<div className={styles['remove-all-container']}>
|
<div className={styles['remove-all-container']}>
|
||||||
<input
|
<input
|
||||||
id='remove_all_detected_conflicts'
|
id="remove_all_detected_conflicts"
|
||||||
name='remove_all_detected_conflicts'
|
name="remove_all_detected_conflicts"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value='all'
|
value="all"
|
||||||
checked={allDetectedConflictsSelectedForRemoval}
|
checked={allDetectedConflictsSelectedForRemoval}
|
||||||
onChange={() => changeCheckForRemoval('all', allDetectedConflicts)}
|
onChange={() => changeCheckForRemoval('all', allDetectedConflicts)}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor='remove_all_detected_conflicts' className={ styles['checkbox-label'] }>
|
<label
|
||||||
|
htmlFor="remove_all_detected_conflicts"
|
||||||
|
className={styles['checkbox-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faCheckSquare}
|
icon={faCheckSquare}
|
||||||
|
@ -181,13 +168,12 @@ export default function UnregisteredClientsView() {
|
||||||
{__('All', 'font-awesome')}
|
{__('All', 'font-awesome')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{allDetectedConflicts.map((md5) => (
|
||||||
allDetectedConflicts.map(md5 => (
|
|
||||||
<tr key={md5}>
|
<tr key={md5}>
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
|
@ -199,7 +185,10 @@ export default function UnregisteredClientsView() {
|
||||||
onChange={() => changeCheckForBlocking(md5)}
|
onChange={() => changeCheckForBlocking(md5)}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={`block_${md5}`} className={ styles['checkbox-label'] }>
|
<label
|
||||||
|
htmlFor={`block_${md5}`}
|
||||||
|
className={styles['checkbox-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faCheckSquare}
|
icon={faCheckSquare}
|
||||||
|
@ -216,27 +205,21 @@ export default function UnregisteredClientsView() {
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
|
<td>{get(unregisteredClients[md5], 'tagName', 'unknown').toLowerCase()}</td>
|
||||||
<td>
|
<td>
|
||||||
{get(unregisteredClients[md5], 'tagName', 'unknown').toLowerCase()}
|
{unregisteredClients[md5].src ||
|
||||||
</td>
|
unregisteredClients[md5].href ||
|
||||||
<td>
|
createInterpolateElement(__('<em>in page source. </em><excerpt/>', 'font-awesome'), {
|
||||||
{
|
|
||||||
unregisteredClients[md5].src
|
|
||||||
|| unregisteredClients[md5].href
|
|
||||||
|| createInterpolateElement(
|
|
||||||
__( '<em>in page source. </em><excerpt/>', 'font-awesome' ),
|
|
||||||
{
|
|
||||||
em: <em />,
|
em: <em />,
|
||||||
excerpt: (
|
excerpt: ((content) =>
|
||||||
( content ) => content
|
content ? (
|
||||||
? <>
|
<>
|
||||||
File starts with: <code>{content}</code>
|
File starts with: <code>{content}</code>
|
||||||
</>
|
</>
|
||||||
: ''
|
) : (
|
||||||
) ( excerpt( get(unregisteredClients[md5], 'innerText') ) )
|
''
|
||||||
}
|
))(excerpt(get(unregisteredClients[md5], 'innerText')))
|
||||||
)
|
})}
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
|
@ -248,7 +231,10 @@ export default function UnregisteredClientsView() {
|
||||||
onChange={() => changeCheckForRemoval(md5)}
|
onChange={() => changeCheckForRemoval(md5)}
|
||||||
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
className={classnames(sharedStyles['sr-only'], sharedStyles['input-checkbox-custom'])}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={`remove_${md5}`} className={ styles['checkbox-label'] }>
|
<label
|
||||||
|
htmlFor={`remove_${md5}`}
|
||||||
|
className={styles['checkbox-label']}
|
||||||
|
>
|
||||||
<span className={sharedStyles['relative']}>
|
<span className={sharedStyles['relative']}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faCheckSquare}
|
icon={faCheckSquare}
|
||||||
|
@ -266,19 +252,21 @@ export default function UnregisteredClientsView() {
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
: <div className={ classnames(sharedStyles['explanation'], sharedStyles['flex'], sharedStyles['flex-row'] )}>
|
) : (
|
||||||
|
<div className={classnames(sharedStyles['explanation'], sharedStyles['flex'], sharedStyles['flex-row'])}>
|
||||||
<div>
|
<div>
|
||||||
<FontAwesomeIcon icon={ faThumbsUp } size='lg'/>
|
<FontAwesomeIcon
|
||||||
|
icon={faThumbsUp}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={ sharedStyles['space-left'] }>
|
<div className={sharedStyles['space-left']}>{__("We haven't detected any plugins or themes trying to load Font Awesome.", 'font-awesome')}</div>
|
||||||
{ __( 'We haven\'t detected any plugins or themes trying to load Font Awesome.', 'font-awesome' ) }
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</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'
|
const get = require('lodash/get')
|
||||||
import set from 'lodash/set'
|
const set = require('lodash/set')
|
||||||
|
|
||||||
// NOTE: the Jest docs on manual mocks indicate that mocks for things under
|
// 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.
|
// node_modules should be in a __mocks__ directory that is adjacent to node_modules.
|
||||||
|
@ -9,7 +9,7 @@ import set from 'lodash/set'
|
||||||
// the root for Jest in such a way that __mocks__ as to live under the src directory.
|
// 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
|
// See: https://github.com/facebook/create-react-app/issues/7539#issuecomment-531463603
|
||||||
|
|
||||||
const DEFAULT_INTERCEPTOR = thing => thing
|
const DEFAULT_INTERCEPTOR = (thing) => thing
|
||||||
const DEFAULT_PUT = (url, _data, _config) => handleRequest({ url, method: 'PUT' })
|
const DEFAULT_PUT = (url, _data, _config) => handleRequest({ url, method: 'PUT' })
|
||||||
const DEFAULT_POST = (url, _data, _config) => handleRequest({ url, method: 'POST' })
|
const DEFAULT_POST = (url, _data, _config) => handleRequest({ url, method: 'POST' })
|
||||||
const DEFAULT_DELETE = (url, _data, _config) => handleRequest({ url, method: 'DELETE' })
|
const DEFAULT_DELETE = (url, _data, _config) => handleRequest({ url, method: 'DELETE' })
|
||||||
|
@ -33,7 +33,7 @@ const axios = {
|
||||||
|
|
||||||
axios.create = () => 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)
|
responses = set(responses, [url, method.toUpperCase()], response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,119 +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='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,33 +0,0 @@
|
||||||
import apiFetch from '@wordpress/api-fetch'
|
|
||||||
|
|
||||||
const configureQueryHandler = params => async (query, variables) => {
|
|
||||||
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',
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ query: query.replace(/\s+/g, " "), variables })
|
|
||||||
} )
|
|
||||||
} 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,9 +1,5 @@
|
||||||
import {
|
import { __experimentalCreateInterpolateElement, createInterpolateElement as stableCreateInterpolateElement } from '@wordpress/element'
|
||||||
__experimentalCreateInterpolateElement,
|
|
||||||
createInterpolateElement as stableCreateInterpolateElement,
|
|
||||||
} from "@wordpress/element";
|
|
||||||
|
|
||||||
const createInterpolateElement = stableCreateInterpolateElement ||
|
const createInterpolateElement = stableCreateInterpolateElement || __experimentalCreateInterpolateElement
|
||||||
__experimentalCreateInterpolateElement;
|
|
||||||
|
|
||||||
export default createInterpolateElement;
|
export default createInterpolateElement
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { createStore } from './store'
|
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__']
|
const initialData = window[GLOBAL_KEY]
|
||||||
// See: https://webpack.js.org/guides/public-path/#on-the-fly
|
// See: https://webpack.js.org/guides/public-path/#on-the-fly
|
||||||
__webpack_public_path__ = get(initialData, 'webpackPublicPath')
|
|
||||||
const CONFLICT_DETECTION_REPORT_EVENT_TYPE = 'fontAwesomeConflictDetectionReport'
|
const CONFLICT_DETECTION_REPORT_EVENT_TYPE = 'fontAwesomeConflictDetectionReport'
|
||||||
/**
|
/**
|
||||||
* This will start out as falsy, when there's a report, we'll set it with those
|
* This will start out as falsy, when there's a report, we'll set it with those
|
||||||
|
@ -16,7 +18,7 @@ const CONFLICT_DETECTION_REPORT_EVENT_TYPE = 'fontAwesomeConflictDetectionReport
|
||||||
let conflictDetectionReport = null
|
let conflictDetectionReport = null
|
||||||
|
|
||||||
if (get(initialData, 'showConflictDetectionReporter')) {
|
if (get(initialData, 'showConflictDetectionReporter')) {
|
||||||
const reportEvent = new Event(CONFLICT_DETECTION_REPORT_EVENT_TYPE, { "bubbles": true, "cancelable": false })
|
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,
|
* If we're doing conflict detection, we must set this up before DOMContentLoaded,
|
||||||
|
@ -24,66 +26,37 @@ if( get(initialData, 'showConflictDetectionReporter') ) {
|
||||||
*/
|
*/
|
||||||
window.FontAwesomeDetection = {
|
window.FontAwesomeDetection = {
|
||||||
...(window.FontAwesomeDetection || {}),
|
...(window.FontAwesomeDetection || {}),
|
||||||
report: params => {
|
report: (params) => {
|
||||||
conflictDetectionReport = params
|
conflictDetectionReport = params
|
||||||
document.dispatchEvent(reportEvent)
|
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) {
|
if (!initialData) {
|
||||||
console.error(__('Font Awesome plugin is broken: initial state data missing.', 'font-awesome'))
|
console.error(__('Font Awesome plugin is broken: initial state data missing.', 'font-awesome'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = createStore(initialData)
|
const store = createStore(initialData)
|
||||||
|
|
||||||
const {
|
set(window, [GLOBAL_KEY, 'createInterpolateElement'], createInterpolateElement)
|
||||||
showAdmin,
|
|
||||||
showConflictDetectionReporter,
|
const { showAdmin, showConflictDetectionReporter } = store.getState()
|
||||||
enableIconChooser,
|
|
||||||
usingCompatJs,
|
|
||||||
isGutenbergPage
|
|
||||||
} = store.getState()
|
|
||||||
|
|
||||||
if (showAdmin) {
|
if (showAdmin) {
|
||||||
import('./mountAdminView')
|
import('./mountAdminView')
|
||||||
.then(({ default: mountAdminView }) => {
|
.then(({ default: mountAdminView }) => {
|
||||||
mountAdminView(store)
|
mountAdminView(store)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error(__('Font Awesome plugin error when initializing admin settings view', 'font-awesome'), error)
|
console.error(__('Font Awesome plugin error when initializing admin settings view', 'font-awesome'), error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showConflictDetectionReporter) {
|
if (showConflictDetectionReporter) {
|
||||||
Promise.all([
|
Promise.all([import('./store/actions'), import('./mountConflictDetectionReporter')])
|
||||||
import('./store/actions'),
|
|
||||||
import('./mountConflictDetectionReporter')
|
|
||||||
])
|
|
||||||
.then(([{ reportDetectedConflicts }, { mountConflictDetectionReporter }]) => {
|
.then(([{ reportDetectedConflicts }, { mountConflictDetectionReporter }]) => {
|
||||||
const report = params => store.dispatch(reportDetectedConflicts(params))
|
const report = (params) => store.dispatch(reportDetectedConflicts(params))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the conflict detection report is already available, just use it;
|
* If the conflict detection report is already available, just use it;
|
||||||
|
@ -92,59 +65,12 @@ if( showConflictDetectionReporter ) {
|
||||||
if (conflictDetectionReport) {
|
if (conflictDetectionReport) {
|
||||||
report(conflictDetectionReport)
|
report(conflictDetectionReport)
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener(
|
document.addEventListener(CONFLICT_DETECTION_REPORT_EVENT_TYPE, (_event) => report(conflictDetectionReport))
|
||||||
CONFLICT_DETECTION_REPORT_EVENT_TYPE,
|
|
||||||
_event => report(conflictDetectionReport)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mountConflictDetectionReporter(store)
|
mountConflictDetectionReporter(store)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error(__('Font Awesome plugin error when initializing conflict detection scanner', 'font-awesome'), 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 })
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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( window['__FontAwesomeOfficialPlugin__setupClassicEditorIconChooser'] ) {
|
|
||||||
setupClassicEditorIconChooser()
|
|
||||||
} else {
|
|
||||||
window['__FontAwesomeOfficialPlugin__setupClassicEditorIconChooser'] = setupClassicEditorIconChooser
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error( __( 'Font Awesome plugin error when initializing Icon Chooser', 'font-awesome' ), error )
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,19 +1,29 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
import ErrorBoundary from './ErrorBoundary'
|
import ErrorBoundary from './ErrorBoundary'
|
||||||
import FontAwesomeAdminView from './FontAwesomeAdminView'
|
import FontAwesomeAdminView from './FontAwesomeAdminView'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import domReady from '@wordpress/dom-ready'
|
import domReady from '@wordpress/dom-ready'
|
||||||
|
|
||||||
|
const isAtLeastReact18 = React.version.split('.')[0] >= 18
|
||||||
|
|
||||||
export default function (store) {
|
export default function (store) {
|
||||||
domReady(() =>
|
const container = document.getElementById('font-awesome-admin')
|
||||||
ReactDOM.render(
|
|
||||||
|
domReady(() => {
|
||||||
|
const app = (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<FontAwesomeAdminView />
|
<FontAwesomeAdminView />
|
||||||
</Provider>
|
</Provider>
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>
|
||||||
document.getElementById('font-awesome-admin')
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (isAtLeastReact18) {
|
||||||
|
createRoot(container).render(app)
|
||||||
|
} else {
|
||||||
|
ReactDOM.render(app, container)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import { createRoot } from 'react-dom/client'
|
||||||
import ConflictDetectionReporter from './ConflictDetectionReporter'
|
import ConflictDetectionReporter from './ConflictDetectionReporter'
|
||||||
import { dom } from '@fortawesome/fontawesome-svg-core'
|
import { dom } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
|
@ -24,15 +24,15 @@ export function mountConflictDetectionReporter(store) {
|
||||||
faStyle.appendChild(cssText)
|
faStyle.appendChild(cssText)
|
||||||
|
|
||||||
const shadowContainer = document.createElement('DIV')
|
const shadowContainer = document.createElement('DIV')
|
||||||
|
const root = createRoot(shadowContainer)
|
||||||
|
|
||||||
shadow.appendChild(faStyle)
|
shadow.appendChild(faStyle)
|
||||||
shadow.appendChild(shadowContainer)
|
shadow.appendChild(shadowContainer)
|
||||||
|
|
||||||
ReactDOM.render(
|
root.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConflictDetectionReporter />
|
<ConflictDetectionReporter />
|
||||||
</Provider>,
|
</Provider>
|
||||||
shadowContainer
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { test as setup, expect } from '@wordpress/e2e-test-utils-playwright'
|
import { test as setup, expect } from '@wordpress/e2e-test-utils-playwright'
|
||||||
import '../support/env.js'
|
import '../support/env.js'
|
||||||
|
|
||||||
const authFile = 'src/playwright/.auth/state.json';
|
const authFile = 'src/playwright/.auth/state.json'
|
||||||
|
|
||||||
setup('authenticate', async ({ page }) => {
|
setup('authenticate', async ({ page }) => {
|
||||||
await page.goto('/wp-login.php');
|
await page.goto('/wp-login.php')
|
||||||
await page.getByLabel('Username or Email Address').fill(process.env.WP_ADMIN_USERNAME);
|
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.getByLabel('Password', { exact: true }).fill(process.env.WP_ADMIN_PASSWORD)
|
||||||
await page.getByRole('button', { name: 'Log In'} ).click();
|
await page.getByRole('button', { name: 'Log In' }).click()
|
||||||
await page.waitForURL('**/wp-admin/');
|
await page.waitForURL('**/wp-admin/')
|
||||||
await page.context().storageState({ path: authFile });
|
await page.context().storageState({ path: authFile })
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,8 +5,8 @@ const CONFIG_ROUTE_PATTERN = '**/font-awesome/v1/config'
|
||||||
const API_ROUTE_PATTERN = '**/font-awesome/v1/api*'
|
const API_ROUTE_PATTERN = '**/font-awesome/v1/api*'
|
||||||
|
|
||||||
setup('pro kit', async ({ page }) => {
|
setup('pro kit', async ({ page }) => {
|
||||||
expect(process.env.API_TOKEN).toBeTruthy();
|
expect(process.env.API_TOKEN).toBeTruthy()
|
||||||
expect(process.env.KIT_TOKEN).toBeTruthy();
|
expect(process.env.KIT_TOKEN).toBeTruthy()
|
||||||
|
|
||||||
await page.goto('/wp-admin/admin.php?page=font-awesome')
|
await page.goto('/wp-admin/admin.php?page=font-awesome')
|
||||||
await page.locator('label').filter({ hasText: 'Use A Kit' }).click()
|
await page.locator('label').filter({ hasText: 'Use A Kit' }).click()
|
||||||
|
@ -23,7 +23,6 @@ setup('pro kit', async ({ page }) => {
|
||||||
await page.getByRole('button').filter({ hasText: 'kits data' }).click()
|
await page.getByRole('button').filter({ hasText: 'kits data' }).click()
|
||||||
await kitsResponsePromise
|
await kitsResponsePromise
|
||||||
|
|
||||||
|
|
||||||
await page.locator('select').selectOption(process.env.KIT_TOKEN)
|
await page.locator('select').selectOption(process.env.KIT_TOKEN)
|
||||||
|
|
||||||
const saveSettingsResponsePromise = page.waitForResponse(CONFIG_ROUTE_PATTERN)
|
const saveSettingsResponsePromise = page.waitForResponse(CONFIG_ROUTE_PATTERN)
|
||||||
|
|
|
@ -10,13 +10,13 @@ setup('reset', async ({ storageState, baseURL }) => {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
user: process.env.WORDPRESS_DB_USER,
|
user: process.env.WORDPRESS_DB_USER,
|
||||||
password: process.env.WORDPRESS_DB_PASSWORD,
|
password: process.env.WORDPRESS_DB_PASSWORD,
|
||||||
database: process.env.WORDPRESS_DB_NAME,
|
database: process.env.WORDPRESS_DB_NAME
|
||||||
})
|
})
|
||||||
|
|
||||||
const sql = 'DELETE FROM `wp_options` WHERE `option_name` = ? LIMIT 1';
|
const sql = 'DELETE FROM `wp_options` WHERE `option_name` = ? LIMIT 1'
|
||||||
await connection.execute(sql, ['font-awesome']);
|
await connection.execute(sql, ['font-awesome'])
|
||||||
await connection.execute(sql, ['font-awesome-conflict-detection']);
|
await connection.execute(sql, ['font-awesome-conflict-detection'])
|
||||||
await connection.execute(sql, ['font-awesome-releases']);
|
await connection.execute(sql, ['font-awesome-releases'])
|
||||||
await requestUtils.activatePlugin('font-awesome')
|
await requestUtils.activatePlugin('font-awesome')
|
||||||
await requestContext.dispose();
|
await requestContext.dispose()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import { request } from '@playwright/test';
|
import { request } from '@playwright/test'
|
||||||
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright'
|
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright'
|
||||||
|
|
||||||
export async function prepareRestApi({ baseURL, storageState }) {
|
export async function prepareRestApi({ baseURL, storageState }) {
|
||||||
const requestContext = await request.newContext({
|
const requestContext = await request.newContext({
|
||||||
baseURL,
|
baseURL
|
||||||
} );
|
})
|
||||||
|
|
||||||
const storageStatePath =
|
const storageStatePath = typeof storageState === 'string' ? storageState : undefined
|
||||||
typeof storageState === 'string' ? storageState : undefined;
|
|
||||||
|
|
||||||
const requestUtils = new RequestUtils(requestContext, {
|
const requestUtils = new RequestUtils(requestContext, {
|
||||||
storageStatePath,
|
storageStatePath
|
||||||
} );
|
})
|
||||||
|
|
||||||
await requestUtils.setupRest()
|
await requestUtils.setupRest()
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import {
|
import { expect, test } from '@wordpress/e2e-test-utils-playwright'
|
||||||
expect,
|
|
||||||
test,
|
|
||||||
} from "@wordpress/e2e-test-utils-playwright";
|
|
||||||
import { prepareRestApi } from '../support/testHelpers'
|
import { prepareRestApi } from '../support/testHelpers'
|
||||||
|
|
||||||
const QUERY = 'query { search(version: "6.x", query: "coffee", first: 1) { id } }'
|
const QUERY = 'query { search(version: "6.x", query: "coffee", first: 1) { id } }'
|
||||||
|
@ -15,11 +12,11 @@ const QUERY = 'query { search(version: "6.x", query: "coffee", first: 1) { id }
|
||||||
// MIME type is known to result in a 403 due to the OWASP default core ruleset
|
// MIME type is known to result in a 403 due to the OWASP default core ruleset
|
||||||
// as of OWASP 4.3.0.
|
// as of OWASP 4.3.0.
|
||||||
test('query as plain text', async ({ storageState, baseURL }) => {
|
test('query as plain text', async ({ storageState, baseURL }) => {
|
||||||
expect(process.env.ENABLE_MOD_SECURITY).toEqual("false")
|
expect(process.env.ENABLE_MOD_SECURITY).toEqual('false')
|
||||||
|
|
||||||
const { requestUtils, requestContext } = await prepareRestApi({ storageState, baseURL })
|
const { requestUtils, requestContext } = await prepareRestApi({ storageState, baseURL })
|
||||||
|
|
||||||
const url = `http://${process.env.WP_DOMAIN}/wp-json/font-awesome/v1/api?_locale=user`;
|
const url = `http://${process.env.WP_DOMAIN}/wp-json/font-awesome/v1/api?_locale=user`
|
||||||
|
|
||||||
const response = await requestUtils.request.fetch(url, {
|
const response = await requestUtils.request.fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -27,11 +24,11 @@ test('query as plain text', async ({ storageState, baseURL }) => {
|
||||||
headers: {
|
headers: {
|
||||||
'X-WP-Nonce': requestUtils.storageState.nonce
|
'X-WP-Nonce': requestUtils.storageState.nonce
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
expect(response.status()).toEqual(200);
|
expect(response.status()).toEqual(200)
|
||||||
|
|
||||||
const responseObj = await response.json();
|
const responseObj = await response.json()
|
||||||
|
|
||||||
expect(responseObj).toHaveProperty('data.search')
|
expect(responseObj).toHaveProperty('data.search')
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,22 +1,15 @@
|
||||||
import {
|
import { expect, test } from '@wordpress/e2e-test-utils-playwright'
|
||||||
expect,
|
|
||||||
test,
|
|
||||||
} from "@wordpress/e2e-test-utils-playwright";
|
|
||||||
|
|
||||||
test("change technology", async ({ page }) => {
|
test('change technology', async ({ page }) => {
|
||||||
await page.goto("/wp-admin/admin.php?page=font-awesome");
|
await page.goto('/wp-admin/admin.php?page=font-awesome')
|
||||||
|
|
||||||
const preferenceCheckResponsePromise = page.waitForResponse(
|
const preferenceCheckResponsePromise = page.waitForResponse('**/font-awesome/v1/preference-check')
|
||||||
"**/font-awesome/v1/preference-check",
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.getByText('SVG').click();
|
await page.getByText('SVG').click()
|
||||||
|
|
||||||
await preferenceCheckResponsePromise;
|
await preferenceCheckResponsePromise
|
||||||
|
|
||||||
const saveChangesResponsePromise = page.waitForResponse(
|
const saveChangesResponsePromise = page.waitForResponse('**/font-awesome/v1/config')
|
||||||
"**/font-awesome/v1/config",
|
await page.getByRole('button', { name: 'Save Changes' }).click()
|
||||||
);
|
await saveChangesResponsePromise
|
||||||
await page.getByRole("button", { name: "Save Changes" }).click();
|
})
|
||||||
await saveChangesResponsePromise;
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,45 +1,37 @@
|
||||||
import {
|
import { Editor, expect, test } from '@wordpress/e2e-test-utils-playwright'
|
||||||
Editor,
|
|
||||||
expect,
|
|
||||||
test,
|
|
||||||
} from "@wordpress/e2e-test-utils-playwright";
|
|
||||||
|
|
||||||
test.describe('full site editor', async () => {
|
test.describe('full site editor', async () => {
|
||||||
test.use({
|
test.use({
|
||||||
editor: async ({ page }, use) => {
|
editor: async ({ page }, use) => {
|
||||||
await use(new Editor({ page }))
|
await use(new Editor({ page }))
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test("insert with icon chooser", async ({ page, editor,pageUtils }) => {
|
test('insert with icon chooser', async ({ page, editor, pageUtils }) => {
|
||||||
const pageLoadPromise = page.waitForResponse(
|
const pageLoadPromise = page.waitForResponse('**/wp/v2/pages*')
|
||||||
'**/wp/v2/pages*'
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.goto("/wp-admin/site-editor.php?canvas=edit");
|
await page.goto('/wp-admin/site-editor.php?canvas=edit')
|
||||||
|
|
||||||
await pageLoadPromise;
|
await pageLoadPromise
|
||||||
|
|
||||||
const getStartedCount = await page.getByRole('button', { name: 'Get started' }).count();
|
const getStartedCount = await page.getByRole('button', { name: 'Get started' }).count()
|
||||||
|
|
||||||
if (getStartedCount > 0) {
|
if (getStartedCount > 0) {
|
||||||
await page.getByRole('button', { name: 'Get started' }).click();
|
await page.getByRole('button', { name: 'Get started' }).click()
|
||||||
}
|
}
|
||||||
|
|
||||||
await editor.insertBlock({
|
await editor.insertBlock({
|
||||||
name: 'core/paragraph',
|
name: 'core/paragraph'
|
||||||
} );
|
})
|
||||||
await page.keyboard.type('Here comes an icon: ')
|
await page.keyboard.type('Here comes an icon: ')
|
||||||
|
|
||||||
await editor.clickBlockToolbarButton('More')
|
await editor.clickBlockToolbarButton('More')
|
||||||
|
|
||||||
await pageUtils.pressKeys('Enter', 1)
|
await pageUtils.pressKeys('Enter', 1)
|
||||||
|
|
||||||
await page.waitForSelector( 'fa-icon-chooser input#search' );
|
await page.waitForSelector('fa-icon-chooser input#search')
|
||||||
|
|
||||||
const searchResponsePromise = page.waitForResponse(
|
const searchResponsePromise = page.waitForResponse('**/font-awesome/v1/api*')
|
||||||
'**/font-awesome/v1/api*'
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.locator('fa-icon-chooser input#search').fill('coffee')
|
await page.locator('fa-icon-chooser input#search').fill('coffee')
|
||||||
|
|
||||||
|
@ -57,6 +49,5 @@ test.describe('full site editor', async () => {
|
||||||
expect(blocks).toHaveLength(1)
|
expect(blocks).toHaveLength(1)
|
||||||
expect(blocks[0].attributes.content).toMatch(/\[icon.*?\]$/)
|
expect(blocks[0].attributes.content).toMatch(/\[icon.*?\]$/)
|
||||||
} catch (_e) {}
|
} catch (_e) {}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
|
@ -8,24 +8,22 @@ test.describe( 'blockEditorIconChooser', async () => {
|
||||||
test.use({
|
test.use({
|
||||||
editor: async ({ page }, use) => {
|
editor: async ({ page }, use) => {
|
||||||
await use(new Editor({ page }))
|
await use(new Editor({ page }))
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('search and select from icon chooser', async ({ editor, page, pageUtils }) => {
|
test('search and select from icon chooser', async ({ editor, page, pageUtils }) => {
|
||||||
await editor.insertBlock({
|
await editor.insertBlock({
|
||||||
name: 'core/paragraph',
|
name: 'core/paragraph'
|
||||||
} );
|
})
|
||||||
await page.keyboard.type('Here comes an icon: ')
|
await page.keyboard.type('Here comes an icon: ')
|
||||||
|
|
||||||
await editor.clickBlockToolbarButton('More')
|
await editor.clickBlockToolbarButton('More')
|
||||||
|
|
||||||
await pageUtils.pressKeys('Enter', 1)
|
await pageUtils.pressKeys('Enter', 1)
|
||||||
|
|
||||||
await page.waitForSelector( 'fa-icon-chooser input#search' );
|
await page.waitForSelector('fa-icon-chooser input#search')
|
||||||
|
|
||||||
const searchResponsePromise = page.waitForResponse(
|
const searchResponsePromise = page.waitForResponse('**/font-awesome/v1/api*')
|
||||||
'**/font-awesome/v1/api*'
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.locator('fa-icon-chooser input#search').fill('coffee')
|
await page.locator('fa-icon-chooser input#search').fill('coffee')
|
||||||
|
|
||||||
|
@ -44,11 +42,7 @@ test.describe( 'blockEditorIconChooser', async () => {
|
||||||
expect(blocks[0].attributes.content).toMatch(/\[icon.*?\]$/)
|
expect(blocks[0].attributes.content).toMatch(/\[icon.*?\]$/)
|
||||||
} catch (_e) {}
|
} catch (_e) {}
|
||||||
|
|
||||||
// The loading of the icon chooser should not have messed up globals.
|
// The loading of the icon chooser should not have messed up the lodash globals.
|
||||||
// It could create problems for other plugins that depend on them.
|
await expect(page.evaluate(() => 'undefined' !== typeof _ && 'undefined' !== typeof lodash)).toBeTruthy()
|
||||||
await expect(page.evaluate(() => _.VERSION == __originalsBeforeFontAwesome._.VERSION)).toBeTruthy();
|
|
||||||
await expect(page.evaluate(() => React.version == __originalsBeforeFontAwesome.React.version)).toBeTruthy();
|
|
||||||
await expect(page.evaluate(() => ReactDOM.version == __originalsBeforeFontAwesome.ReactDOM.version)).toBeTruthy();
|
|
||||||
await expect(page.evaluate(() => moment.version == __originalsBeforeFontAwesome.moment.version)).toBeTruthy();
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,62 +1,41 @@
|
||||||
import {
|
import { expect, test } from '@wordpress/e2e-test-utils-playwright'
|
||||||
expect,
|
|
||||||
test,
|
|
||||||
} from "@wordpress/e2e-test-utils-playwright";
|
|
||||||
|
|
||||||
test.describe("conflictScanner", async () => {
|
test.describe('conflictScanner', async () => {
|
||||||
test.beforeEach(async ({ requestUtils }) => {
|
test.beforeEach(async ({ requestUtils }) => {
|
||||||
await requestUtils.activatePlugin("plugin-gamma");
|
await requestUtils.activatePlugin('plugin-gamma')
|
||||||
});
|
})
|
||||||
|
|
||||||
test.afterEach(async ({ requestUtils }) => {
|
test.afterEach(async ({ requestUtils }) => {
|
||||||
await requestUtils.deactivatePlugin("plugin-gamma");
|
await requestUtils.deactivatePlugin('plugin-gamma')
|
||||||
});
|
})
|
||||||
|
|
||||||
test("start conflict detection scanner, detect, and block", async ({ page }) => {
|
test('start conflict detection scanner, detect, and block', async ({ page }) => {
|
||||||
await page.goto("/wp-admin/admin.php?page=font-awesome");
|
await page.goto('/wp-admin/admin.php?page=font-awesome')
|
||||||
await page.getByRole("button", { name: "Troubleshoot" }).click();
|
await page.getByRole('button', { name: 'Troubleshoot' }).click()
|
||||||
const scannerStartResponsePromise = page.waitForResponse(
|
const scannerStartResponsePromise = page.waitForResponse('**/font-awesome/v1/conflict-detection/until')
|
||||||
"**/font-awesome/v1/conflict-detection/until",
|
await page.getByRole('button', { name: 'Enable scanner for 10 minutes' }).click()
|
||||||
);
|
await scannerStartResponsePromise
|
||||||
await page.getByRole("button", { name: "Enable scanner for 10 minutes" })
|
|
||||||
.click();
|
|
||||||
await scannerStartResponsePromise;
|
|
||||||
|
|
||||||
await expect(
|
await expect(page.getByRole('heading', { name: 'Font Awesome Conflict Scanner' })).toBeVisible()
|
||||||
page.getByRole("heading", { name: "Font Awesome Conflict Scanner" }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await expect(
|
await expect(page.locator('span').filter({ hasText: 'minutes left to browse' }).first()).toBeVisible()
|
||||||
page.locator("span").filter({ hasText: "minutes left to browse" })
|
|
||||||
.first(),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
const scannerReportResponsePromise = page.waitForResponse(
|
const scannerReportResponsePromise = page.waitForResponse('**/font-awesome/v1/conflict-detection/conflicts')
|
||||||
"**/font-awesome/v1/conflict-detection/conflicts",
|
await page.goto('/')
|
||||||
);
|
await expect(page.getByRole('heading', { name: 'Plugin Gamma' })).toBeVisible()
|
||||||
await page.goto("/");
|
|
||||||
await expect(page.getByRole("heading", { name: "Plugin Gamma" }))
|
|
||||||
.toBeVisible();
|
|
||||||
|
|
||||||
await scannerReportResponsePromise;
|
await scannerReportResponsePromise
|
||||||
await expect(
|
await expect(page.getByRole('heading', { name: 'Font Awesome Conflict Scanner' })).toBeVisible()
|
||||||
page.getByRole("heading", { name: "Font Awesome Conflict Scanner" }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await expect(page.getByText("Page scan complete")).toBeVisible();
|
await expect(page.getByText('Page scan complete')).toBeVisible()
|
||||||
await expect(page.getByText("1 new conflicts found on this page"))
|
await expect(page.getByText('1 new conflicts found on this page')).toBeVisible()
|
||||||
.toBeVisible();
|
await expect(page.getByText('1 total found')).toBeVisible()
|
||||||
await expect(page.getByText("1 total found")).toBeVisible();
|
await page.getByRole('link', { name: 'manage' }).click()
|
||||||
await page.getByRole("link", { name: "manage" }).click();
|
await page.getByRole('row', { name: 'link https://cdn.jsdelivr.net' }).locator('svg').nth(1).click()
|
||||||
await page.getByRole("row", { name: "link https://cdn.jsdelivr.net" })
|
const saveChangesResponsePromise = page.waitForResponse('**/font-awesome/v1/conflict-detection/conflicts/blocklist')
|
||||||
.locator("svg").nth(1).click();
|
await page.getByRole('button', { name: 'Save Changes' }).click()
|
||||||
const saveChangesResponsePromise = page.waitForResponse(
|
await saveChangesResponsePromise
|
||||||
"**/font-awesome/v1/conflict-detection/conflicts/blocklist",
|
await page.goto('/')
|
||||||
);
|
await expect(page.getByText('0 new conflicts found on this page')).toBeVisible()
|
||||||
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) {
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
getCLS(onPerfEntry);
|
getCLS(onPerfEntry)
|
||||||
getFID(onPerfEntry);
|
getFID(onPerfEntry)
|
||||||
getFCP(onPerfEntry);
|
getFCP(onPerfEntry)
|
||||||
getLCP(onPerfEntry);
|
getLCP(onPerfEntry)
|
||||||
getTTFB(onPerfEntry);
|
getTTFB(onPerfEntry)
|
||||||
});
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export default reportWebVitals;
|
export default reportWebVitals
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// allows you to do things like:
|
// allows you to do things like:
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
import Enzyme from 'enzyme'
|
import Enzyme from 'enzyme'
|
||||||
import Adapter from 'enzyme-adapter-react-16'
|
import Adapter from 'enzyme-adapter-react-16'
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import toPairs from 'lodash/toPairs'
|
import { toPairs, size, get, has, find } from 'lodash'
|
||||||
import size from 'lodash/size'
|
|
||||||
import get from 'lodash/get'
|
|
||||||
import find from 'lodash/find'
|
|
||||||
import reportRequestError, { redactRequestData, redactHeaders } from '../util/reportRequestError'
|
import reportRequestError, { redactRequestData, redactHeaders } from '../util/reportRequestError'
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
import has from 'lodash/has'
|
|
||||||
import sliceJson from '../util/sliceJson'
|
import sliceJson from '../util/sliceJson'
|
||||||
|
import { clearQueryCache } from '../queryCache'
|
||||||
|
|
||||||
const restApiAxios = axios.create()
|
const restApiAxios = axios.create()
|
||||||
|
|
||||||
|
@ -20,13 +17,16 @@ export const CONFLICT_DETECTION_SCANNER_DURATION_MIN = 10
|
||||||
// (which would just be exactly "now").
|
// (which would just be exactly "now").
|
||||||
const CONFLICT_DETECTION_SCANNER_DEACTIVATION_DELTA_MS = 1
|
const CONFLICT_DETECTION_SCANNER_DEACTIVATION_DELTA_MS = 1
|
||||||
|
|
||||||
const COULD_NOT_SAVE_CHANGES_MESSAGE = __( 'Couldn\'t save those changes', 'font-awesome' )
|
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 REJECTED_METHOD_COULD_NOT_SAVE_CHANGES_MESSAGE = __(
|
||||||
const COULD_NOT_CHECK_PREFERENCES_MESSAGE = __( 'Couldn\'t check preferences', 'font-awesome' )
|
'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 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 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_START_SCANNER_MESSAGE = __("Couldn't start the scanner", 'font-awesome')
|
||||||
const COULD_NOT_SNOOZE_MESSAGE = __( 'Couldn\'t snooze', 'font-awesome' )
|
const COULD_NOT_SNOOZE_MESSAGE = __("Couldn't snooze", 'font-awesome')
|
||||||
|
|
||||||
export function preprocessResponse(response) {
|
export function preprocessResponse(response) {
|
||||||
const confirmed = has(response, 'headers.fontawesome-confirmation')
|
const confirmed = has(response, 'headers.fontawesome-confirmation')
|
||||||
|
@ -42,9 +42,7 @@ export function preprocessResponse( response ) {
|
||||||
|
|
||||||
const foundUnexpectedData = 'string' === typeof data && size(data) > 0
|
const foundUnexpectedData = 'string' === typeof data && size(data) > 0
|
||||||
|
|
||||||
const sliced = foundUnexpectedData
|
const sliced = foundUnexpectedData ? sliceJson(data) : {}
|
||||||
? sliceJson( data )
|
|
||||||
: {}
|
|
||||||
|
|
||||||
// Fixup the response data if garbage was fixed
|
// Fixup the response data if garbage was fixed
|
||||||
if (foundUnexpectedData) {
|
if (foundUnexpectedData) {
|
||||||
|
@ -149,8 +147,8 @@ export function preprocessResponse( response ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
restApiAxios.interceptors.response.use(
|
restApiAxios.interceptors.response.use(
|
||||||
response => preprocessResponse( response ),
|
(response) => preprocessResponse(response),
|
||||||
error => {
|
(error) => {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
error.response = preprocessResponse(error.response)
|
error.response = preprocessResponse(error.response)
|
||||||
error.uiMessage = get(error, 'response.uiMessage')
|
error.uiMessage = get(error, 'response.uiMessage')
|
||||||
|
@ -255,15 +253,14 @@ export function submitPendingUnregisteredClientDeletions() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return restApiAxios.delete(
|
return restApiAxios
|
||||||
`${apiUrl}/conflict-detection/conflicts`,
|
.delete(`${apiUrl}/conflict-detection/conflicts`, {
|
||||||
{
|
|
||||||
data: deleteList,
|
data: deleteList,
|
||||||
headers: {
|
headers: {
|
||||||
'X-WP-Nonce': apiNonce
|
'X-WP-Nonce': apiNonce
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
).then(response => {
|
.then((response) => {
|
||||||
const { status, data, falsePositive } = response
|
const { status, data, falsePositive } = response
|
||||||
|
|
||||||
if (falsePositive) {
|
if (falsePositive) {
|
||||||
|
@ -276,7 +273,8 @@ export function submitPendingUnregisteredClientDeletions() {
|
||||||
message: ''
|
message: ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).catch(handleError)
|
})
|
||||||
|
.catch(handleError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,15 +302,13 @@ export function submitPendingBlocklist() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return restApiAxios.post(
|
return restApiAxios
|
||||||
`${apiUrl}/conflict-detection/conflicts/blocklist`,
|
.post(`${apiUrl}/conflict-detection/conflicts/blocklist`, blocklist, {
|
||||||
blocklist,
|
|
||||||
{
|
|
||||||
headers: {
|
headers: {
|
||||||
'X-WP-Nonce': apiNonce
|
'X-WP-Nonce': apiNonce
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
).then(response => {
|
.then((response) => {
|
||||||
const { status, data, falsePositive } = response
|
const { status, data, falsePositive } = response
|
||||||
|
|
||||||
if (falsePositive) {
|
if (falsePositive) {
|
||||||
|
@ -325,7 +321,8 @@ export function submitPendingBlocklist() {
|
||||||
message: ''
|
message: ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).catch(handleError)
|
})
|
||||||
|
.catch(handleError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +339,8 @@ export function checkPreferenceConflicts() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return restApiAxios.post(
|
return restApiAxios
|
||||||
|
.post(
|
||||||
`${apiUrl}/preference-check`,
|
`${apiUrl}/preference-check`,
|
||||||
{ ...options, ...pendingOptions },
|
{ ...options, ...pendingOptions },
|
||||||
{
|
{
|
||||||
|
@ -350,7 +348,8 @@ export function checkPreferenceConflicts() {
|
||||||
'X-WP-Nonce': apiNonce
|
'X-WP-Nonce': apiNonce
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).then(response => {
|
)
|
||||||
|
.then((response) => {
|
||||||
const { data, falsePositive } = response
|
const { data, falsePositive } = response
|
||||||
|
|
||||||
if (falsePositive) {
|
if (falsePositive) {
|
||||||
|
@ -363,7 +362,8 @@ export function checkPreferenceConflicts() {
|
||||||
detectedConflicts: data
|
detectedConflicts: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).catch(handleError)
|
})
|
||||||
|
.catch(handleError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +374,7 @@ export function chooseAwayFromKitConfig({ activeKitToken }) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'CHOOSE_AWAY_FROM_KIT_CONFIG',
|
type: 'CHOOSE_AWAY_FROM_KIT_CONFIG',
|
||||||
activeKitToken,
|
activeKitToken,
|
||||||
concreteVersion: get(releases, 'latest_version_6')
|
concreteVersion: get(releases, 'latest_version_7')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,6 +391,8 @@ export function queryKits() {
|
||||||
|
|
||||||
dispatch({ type: 'KITS_QUERY_START' })
|
dispatch({ type: 'KITS_QUERY_START' })
|
||||||
|
|
||||||
|
clearQueryCache()
|
||||||
|
|
||||||
const handleKitsQueryError = ({ uiMessage }) => {
|
const handleKitsQueryError = ({ uiMessage }) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'KITS_QUERY_END',
|
type: 'KITS_QUERY_END',
|
||||||
|
@ -403,11 +405,12 @@ export function queryKits() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'OPTIONS_FORM_SUBMIT_END',
|
type: 'OPTIONS_FORM_SUBMIT_END',
|
||||||
success: false,
|
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(
|
return restApiAxios
|
||||||
|
.post(
|
||||||
`${apiUrl}/api`,
|
`${apiUrl}/api`,
|
||||||
'query { me { kits { name version technologySelected licenseSelected minified token shimEnabled autoAccessibilityEnabled status }}}',
|
'query { me { kits { name version technologySelected licenseSelected minified token shimEnabled autoAccessibilityEnabled status }}}',
|
||||||
{
|
{
|
||||||
|
@ -415,7 +418,8 @@ export function queryKits() {
|
||||||
'X-WP-Nonce': apiNonce
|
'X-WP-Nonce': apiNonce
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).then(response => {
|
)
|
||||||
|
.then((response) => {
|
||||||
if (response.falsePositive) return handleKitsQueryError(response)
|
if (response.falsePositive) return handleKitsQueryError(response)
|
||||||
|
|
||||||
const data = get(response, 'data.data')
|
const data = get(response, 'data.data')
|
||||||
|
@ -478,11 +482,13 @@ export function queryKits() {
|
||||||
|
|
||||||
dispatch({ type: 'OPTIONS_FORM_SUBMIT_START' })
|
dispatch({ type: 'OPTIONS_FORM_SUBMIT_START' })
|
||||||
|
|
||||||
return restApiAxios.post(
|
return restApiAxios
|
||||||
|
.post(
|
||||||
`${apiUrl}/config`,
|
`${apiUrl}/config`,
|
||||||
{
|
{
|
||||||
options: {
|
options: {
|
||||||
...options, ...optionsUpdate
|
...options,
|
||||||
|
...optionsUpdate
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -490,7 +496,8 @@ export function queryKits() {
|
||||||
'X-WP-Nonce': apiNonce
|
'X-WP-Nonce': apiNonce
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).then(response => {
|
)
|
||||||
|
.then((response) => {
|
||||||
const { data, falsePositive } = response
|
const { data, falsePositive } = response
|
||||||
|
|
||||||
if (falsePositive) return handleKitUpdateError(response)
|
if (falsePositive) return handleKitUpdateError(response)
|
||||||
|
@ -501,8 +508,10 @@ export function queryKits() {
|
||||||
success: true,
|
success: true,
|
||||||
message: __('Kit changes saved', 'font-awesome')
|
message: __('Kit changes saved', 'font-awesome')
|
||||||
})
|
})
|
||||||
}).catch(handleKitUpdateError)
|
})
|
||||||
}).catch(handleKitsQueryError)
|
.catch(handleKitUpdateError)
|
||||||
|
})
|
||||||
|
.catch(handleKitsQueryError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,7 +529,8 @@ export function submitPendingOptions() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return restApiAxios.post(
|
return restApiAxios
|
||||||
|
.post(
|
||||||
`${apiUrl}/config`,
|
`${apiUrl}/config`,
|
||||||
{ options: { ...options, ...pendingOptions } },
|
{ options: { ...options, ...pendingOptions } },
|
||||||
{
|
{
|
||||||
|
@ -528,7 +538,8 @@ export function submitPendingOptions() {
|
||||||
'X-WP-Nonce': apiNonce
|
'X-WP-Nonce': apiNonce
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).then(response => {
|
)
|
||||||
|
.then((response) => {
|
||||||
const { data, falsePositive } = response
|
const { data, falsePositive } = response
|
||||||
|
|
||||||
if (falsePositive) {
|
if (falsePositive) {
|
||||||
|
@ -541,7 +552,8 @@ export function submitPendingOptions() {
|
||||||
message: __('Changes saved', 'font-awesome')
|
message: __('Changes saved', 'font-awesome')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).catch(handleError)
|
})
|
||||||
|
.catch(handleError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,7 +571,8 @@ export function updateApiToken({ apiToken = false, runQueryKits = false }) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return restApiAxios.post(
|
return restApiAxios
|
||||||
|
.post(
|
||||||
`${apiUrl}/config`,
|
`${apiUrl}/config`,
|
||||||
{ options: { ...options, apiToken } },
|
{ options: { ...options, apiToken } },
|
||||||
{
|
{
|
||||||
|
@ -567,7 +580,8 @@ export function updateApiToken({ apiToken = false, runQueryKits = false }) {
|
||||||
'X-WP-Nonce': apiNonce
|
'X-WP-Nonce': apiNonce
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).then(response => {
|
)
|
||||||
|
.then((response) => {
|
||||||
const { data, falsePositive } = response
|
const { data, falsePositive } = response
|
||||||
|
|
||||||
if (falsePositive) {
|
if (falsePositive) {
|
||||||
|
@ -584,7 +598,8 @@ export function updateApiToken({ apiToken = false, runQueryKits = false }) {
|
||||||
return dispatch(queryKits())
|
return dispatch(queryKits())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch(handleError)
|
})
|
||||||
|
.catch(handleError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,16 +642,13 @@ export function reportDetectedConflicts({ nodesTested = {} }) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return restApiAxios.post(
|
return restApiAxios
|
||||||
`${apiUrl}/conflict-detection/conflicts`,
|
.post(`${apiUrl}/conflict-detection/conflicts`, payload, {
|
||||||
payload,
|
|
||||||
{
|
|
||||||
headers: {
|
headers: {
|
||||||
'X-WP-Nonce': apiNonce
|
'X-WP-Nonce': apiNonce
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
.then((response) => {
|
||||||
.then(response => {
|
|
||||||
const { status, data, falsePositive } = response
|
const { status, data, falsePositive } = response
|
||||||
|
|
||||||
if (falsePositive) {
|
if (falsePositive) {
|
||||||
|
@ -650,7 +662,7 @@ export function reportDetectedConflicts({ nodesTested = {} }) {
|
||||||
* response with garbage in it had an erroneous HTTP 200 status
|
* response with garbage in it had an erroneous HTTP 200 status
|
||||||
* on it, but no parseable JSON, which is equivalent to a 204.
|
* on it, but no parseable JSON, which is equivalent to a 204.
|
||||||
*/
|
*/
|
||||||
data: ( 204 === status || 0 === size(data) ) ? null : data
|
data: 204 === status || 0 === size(data) ? null : data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -661,47 +673,6 @@ export function reportDetectedConflicts({ nodesTested = {} }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.post(
|
|
||||||
`${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) {
|
export function setActiveAdminTab(tab) {
|
||||||
return {
|
return {
|
||||||
type: 'SET_ACTIVE_ADMIN_TAB',
|
type: 'SET_ACTIVE_ADMIN_TAB',
|
||||||
|
@ -713,13 +684,9 @@ export function setConflictDetectionScanner({ enable = true }) {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
const { apiNonce, apiUrl } = getState()
|
const { apiNonce, apiUrl } = getState()
|
||||||
|
|
||||||
const actionStartType = enable
|
const actionStartType = enable ? 'ENABLE_CONFLICT_DETECTION_SCANNER_START' : 'DISABLE_CONFLICT_DETECTION_SCANNER_START'
|
||||||
? 'ENABLE_CONFLICT_DETECTION_SCANNER_START'
|
|
||||||
: 'DISABLE_CONFLICT_DETECTION_SCANNER_START'
|
|
||||||
|
|
||||||
const actionEndType = enable
|
const actionEndType = enable ? 'ENABLE_CONFLICT_DETECTION_SCANNER_END' : 'DISABLE_CONFLICT_DETECTION_SCANNER_END'
|
||||||
? 'ENABLE_CONFLICT_DETECTION_SCANNER_END'
|
|
||||||
: 'DISABLE_CONFLICT_DETECTION_SCANNER_END'
|
|
||||||
|
|
||||||
dispatch({ type: actionStartType })
|
dispatch({ type: actionStartType })
|
||||||
|
|
||||||
|
@ -731,17 +698,19 @@ export function setConflictDetectionScanner({ enable = true }) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return restApiAxios.post(
|
return restApiAxios
|
||||||
|
.post(
|
||||||
`${apiUrl}/conflict-detection/until`,
|
`${apiUrl}/conflict-detection/until`,
|
||||||
enable
|
enable
|
||||||
? Math.floor((new Date((new Date()).valueOf() + (CONFLICT_DETECTION_SCANNER_DURATION_MIN * 1000 * 60))) / 1000)
|
? 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,
|
: Math.floor(new Date() / 1000) - CONFLICT_DETECTION_SCANNER_DEACTIVATION_DELTA_MS,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'X-WP-Nonce': apiNonce
|
'X-WP-Nonce': apiNonce
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).then(response => {
|
)
|
||||||
|
.then((response) => {
|
||||||
const { status, data, falsePositive } = response
|
const { status, data, falsePositive } = response
|
||||||
|
|
||||||
if (falsePositive) {
|
if (falsePositive) {
|
||||||
|
@ -753,6 +722,7 @@ export function setConflictDetectionScanner({ enable = true }) {
|
||||||
success: true
|
success: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).catch(handleError)
|
})
|
||||||
|
.catch(handleError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,14 +30,18 @@ describe('addPendingOption', () => {
|
||||||
test('when multiple pending options are adjusted together, all are updated', () => {
|
test('when multiple pending options are adjusted together, all are updated', () => {
|
||||||
store.dispatch(addPendingOption({ technology: 'webfont', pseudoElements: true }))
|
store.dispatch(addPendingOption({ technology: 'webfont', pseudoElements: true }))
|
||||||
expect(store.getActions().length).toEqual(2)
|
expect(store.getActions().length).toEqual(2)
|
||||||
expect(store.getActions()[0]).toEqual(expect.objectContaining({
|
expect(store.getActions()[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
type: 'ADD_PENDING_OPTION',
|
type: 'ADD_PENDING_OPTION',
|
||||||
change: { technology: 'webfont' }
|
change: { technology: 'webfont' }
|
||||||
}))
|
})
|
||||||
expect(store.getActions()[1]).toEqual(expect.objectContaining({
|
)
|
||||||
|
expect(store.getActions()[1]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
type: 'ADD_PENDING_OPTION',
|
type: 'ADD_PENDING_OPTION',
|
||||||
change: { pseudoElements: true }
|
change: { pseudoElements: true }
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -74,11 +78,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
options: pendingOptions,
|
options: pendingOptions,
|
||||||
error: {
|
error: {
|
||||||
errors: {
|
errors: {
|
||||||
"code1": ["message1"],
|
code1: ['message1']
|
||||||
},
|
},
|
||||||
error_data: {
|
error_data: {
|
||||||
"code1": {
|
code1: {
|
||||||
"trace": 'some stack trace'
|
trace: 'some stack trace'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,10 +102,13 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('submits successfully with successful ui message and also reports error to console', done => {
|
test('submits successfully with successful ui message and also reports error to console', (done) => {
|
||||||
store.dispatch(submitPendingOptions()).then(() => {
|
store
|
||||||
|
.dispatch(submitPendingOptions())
|
||||||
|
.then(() => {
|
||||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
expect(reportRequestError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
error: expect.objectContaining({
|
error: expect.objectContaining({
|
||||||
errors: {
|
errors: {
|
||||||
code1: expect.anything()
|
code1: expect.anything()
|
||||||
|
@ -112,9 +119,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
}),
|
}),
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
ok: true
|
ok: true
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
expect(store.getActions().length).toEqual(2)
|
expect(store.getActions().length).toEqual(2)
|
||||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
expect(store.getActions()).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||||
}),
|
}),
|
||||||
|
@ -124,10 +133,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
data,
|
data,
|
||||||
message: expect.stringContaining('saved')
|
message: expect.stringContaining('saved')
|
||||||
})
|
})
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(e => done(e))
|
.catch((e) => done(e))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -152,17 +162,22 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('reports warning but completes successfully', done => {
|
test('reports warning but completes successfully', (done) => {
|
||||||
store.dispatch(submitPendingOptions()).then(() => {
|
store
|
||||||
|
.dispatch(submitPendingOptions())
|
||||||
|
.then(() => {
|
||||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
expect(reportRequestError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
error: null,
|
error: null,
|
||||||
confirmed: false,
|
confirmed: false,
|
||||||
ok: true,
|
ok: true,
|
||||||
trimmed: INVALID_JSON_RESPONSE_DATA
|
trimmed: INVALID_JSON_RESPONSE_DATA
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
expect(store.getActions().length).toEqual(2)
|
expect(store.getActions().length).toEqual(2)
|
||||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
expect(store.getActions()).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||||
}),
|
}),
|
||||||
|
@ -172,10 +187,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
data,
|
data,
|
||||||
message: expect.stringContaining('saved')
|
message: expect.stringContaining('saved')
|
||||||
})
|
})
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(e => done(e))
|
.catch((e) => done(e))
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('axios request', () => {
|
describe('axios request', () => {
|
||||||
|
@ -185,8 +201,10 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
changeImpl({ name: 'post', fn: mockPost })
|
changeImpl({ name: 'post', fn: mockPost })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('submits pendingOptions', done => {
|
test('submits pendingOptions', (done) => {
|
||||||
store.dispatch(submitPendingOptions()).then(() => {
|
store
|
||||||
|
.dispatch(submitPendingOptions())
|
||||||
|
.then(() => {
|
||||||
expect(mockPost).toHaveBeenCalledTimes(1)
|
expect(mockPost).toHaveBeenCalledTimes(1)
|
||||||
expect(mockPost).toHaveBeenCalledWith(
|
expect(mockPost).toHaveBeenCalledWith(
|
||||||
`${apiUrl}/config`,
|
`${apiUrl}/config`,
|
||||||
|
@ -201,7 +219,7 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
)
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(e => done(e))
|
.catch((e) => done(e))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -238,15 +256,18 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
data: requestData,
|
data: requestData,
|
||||||
headers: requestHeaders
|
headers: requestHeaders
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('displays default ui message and emits console message', done => {
|
test('displays default ui message and emits console message', (done) => {
|
||||||
reportRequestError.mockReturnValueOnce(null)
|
reportRequestError.mockReturnValueOnce(null)
|
||||||
store.dispatch(submitPendingOptions()).then(() => {
|
store
|
||||||
|
.dispatch(submitPendingOptions())
|
||||||
|
.then(() => {
|
||||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
expect(reportRequestError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
requestMethod: method,
|
requestMethod: method,
|
||||||
//requestData,
|
//requestData,
|
||||||
|
@ -256,9 +277,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
responseStatus: status,
|
responseStatus: status,
|
||||||
responseStatusText: statusText,
|
responseStatusText: statusText,
|
||||||
responseData
|
responseData
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
expect(store.getActions().length).toEqual(2)
|
expect(store.getActions().length).toEqual(2)
|
||||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
expect(store.getActions()).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||||
}),
|
}),
|
||||||
|
@ -267,10 +290,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
success: false,
|
success: false,
|
||||||
message: expect.stringContaining("Couldn't save")
|
message: expect.stringContaining("Couldn't save")
|
||||||
})
|
})
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(e => done(e))
|
.catch((e) => done(e))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -286,10 +310,13 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
reportRequestError.mockImplementation(() => MOCK_UI_MESSAGE)
|
reportRequestError.mockImplementation(() => MOCK_UI_MESSAGE)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('failed request is reported to console and failure with uiMessage is dispatched to store', done => {
|
test('failed request is reported to console and failure with uiMessage is dispatched to store', (done) => {
|
||||||
store.dispatch(submitPendingOptions()).then(() => {
|
store
|
||||||
|
.dispatch(submitPendingOptions())
|
||||||
|
.then(() => {
|
||||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
expect(reportRequestError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
error: {
|
error: {
|
||||||
errors: expect.objectContaining({
|
errors: expect.objectContaining({
|
||||||
fontawesome_request_noresponse: [expect.any(String)]
|
fontawesome_request_noresponse: [expect.any(String)]
|
||||||
|
@ -299,9 +326,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
request: expect.any(XMLHttpRequest)
|
request: expect.any(XMLHttpRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}))
|
})
|
||||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
)
|
||||||
|
expect(store.getActions()).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||||
}),
|
}),
|
||||||
|
@ -310,10 +339,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
success: false,
|
success: false,
|
||||||
message: MOCK_UI_MESSAGE
|
message: MOCK_UI_MESSAGE
|
||||||
})
|
})
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(e => done(e))
|
.catch((e) => done(e))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -328,10 +358,13 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
reportRequestError.mockImplementation(() => MOCK_UI_MESSAGE)
|
reportRequestError.mockImplementation(() => MOCK_UI_MESSAGE)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('failure is reported to console and failure with uiMessage is dispatched to store', done => {
|
test('failure is reported to console and failure with uiMessage is dispatched to store', (done) => {
|
||||||
store.dispatch(submitPendingOptions()).then(() => {
|
store
|
||||||
|
.dispatch(submitPendingOptions())
|
||||||
|
.then(() => {
|
||||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
expect(reportRequestError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
error: {
|
error: {
|
||||||
errors: expect.objectContaining({
|
errors: expect.objectContaining({
|
||||||
fontawesome_request_failed: [expect.stringContaining('server failed')]
|
fontawesome_request_failed: [expect.stringContaining('server failed')]
|
||||||
|
@ -341,9 +374,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
failedRequestMessage: 'some axios error'
|
failedRequestMessage: 'some axios error'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}))
|
})
|
||||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
)
|
||||||
|
expect(store.getActions()).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: 'OPTIONS_FORM_SUBMIT_START'
|
type: 'OPTIONS_FORM_SUBMIT_START'
|
||||||
}),
|
}),
|
||||||
|
@ -352,10 +387,11 @@ describe('submitPendingOptions and interceptors', () => {
|
||||||
success: false,
|
success: false,
|
||||||
message: MOCK_UI_MESSAGE
|
message: MOCK_UI_MESSAGE
|
||||||
})
|
})
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(e => done(e))
|
.catch((e) => done(e))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -364,7 +400,6 @@ describe('some action failure cases', () => {
|
||||||
const STATE_TECH_CHANGE = {
|
const STATE_TECH_CHANGE = {
|
||||||
options: {
|
options: {
|
||||||
technology: 'webfont'
|
technology: 'webfont'
|
||||||
|
|
||||||
},
|
},
|
||||||
pendingOptions: {
|
pendingOptions: {
|
||||||
technology: 'svg'
|
technology: 'svg'
|
||||||
|
@ -435,20 +470,11 @@ describe('some action failure cases', () => {
|
||||||
params: {
|
params: {
|
||||||
nodesTested: {
|
nodesTested: {
|
||||||
conflict: {
|
conflict: {
|
||||||
'abc123': {}
|
abc123: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
action: 'snoozeV3DeprecationWarning',
|
|
||||||
state: {},
|
|
||||||
route: 'v3deprecation',
|
|
||||||
method: 'POST',
|
|
||||||
startAction: 'SNOOZE_V3DEPRECATION_WARNING_START',
|
|
||||||
endAction: 'SNOOZE_V3DEPRECATION_WARNING_END',
|
|
||||||
params: {}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
action: 'setConflictDetectionScanner',
|
action: 'setConflictDetectionScanner',
|
||||||
desc: 'when enabling',
|
desc: 'when enabling',
|
||||||
|
@ -493,11 +519,11 @@ describe('some action failure cases', () => {
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
errors: {
|
errors: {
|
||||||
"code1": ["message1"],
|
code1: ['message1']
|
||||||
},
|
},
|
||||||
error_data: {
|
error_data: {
|
||||||
"code1": {
|
code1: {
|
||||||
"trace": 'some stack trace'
|
trace: 'some stack trace'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -510,7 +536,7 @@ describe('some action failure cases', () => {
|
||||||
resetAxiosMocks()
|
resetAxiosMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
cases.map(c => {
|
cases.map((c) => {
|
||||||
describe(`${c.action}${c.desc || ''}`, () => {
|
describe(`${c.action}${c.desc || ''}`, () => {
|
||||||
let store = null
|
let store = null
|
||||||
|
|
||||||
|
@ -530,26 +556,31 @@ describe('some action failure cases', () => {
|
||||||
response: {
|
response: {
|
||||||
status: 200,
|
status: 200,
|
||||||
statusText: 'OK',
|
statusText: 'OK',
|
||||||
data: `${garbage}${JSON.stringify(data)}`,
|
data: `${garbage}${JSON.stringify(data)}`
|
||||||
// no confirmation header
|
// no confirmation header
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('reports warning and dispatches a failure action despite the garbage', done => {
|
test('reports warning and dispatches a failure action despite the garbage', (done) => {
|
||||||
store.dispatch(actions[c.action](c.params)).then(() => {
|
store
|
||||||
|
.dispatch(actions[c.action](c.params))
|
||||||
|
.then(() => {
|
||||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
expect(reportRequestError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
error: expect.objectContaining({
|
error: expect.objectContaining({
|
||||||
errors: expect.anything(),
|
errors: expect.anything(),
|
||||||
'error_data': expect.anything()
|
error_data: expect.anything()
|
||||||
}),
|
}),
|
||||||
confirmed: false,
|
confirmed: false,
|
||||||
falsePositive: true,
|
falsePositive: true,
|
||||||
trimmed: garbage
|
trimmed: garbage
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
expect(store.getActions().length).toEqual(2)
|
expect(store.getActions().length).toEqual(2)
|
||||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
expect(store.getActions()).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: c.startAction
|
type: c.startAction
|
||||||
}),
|
}),
|
||||||
|
@ -558,10 +589,11 @@ describe('some action failure cases', () => {
|
||||||
success: false,
|
success: false,
|
||||||
message: expect.stringMatching(/[a-z]/)
|
message: expect.stringMatching(/[a-z]/)
|
||||||
})
|
})
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(e => done(e))
|
.catch((e) => done(e))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -581,20 +613,25 @@ describe('some action failure cases', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('reports ui and console error messages', done => {
|
test('reports ui and console error messages', (done) => {
|
||||||
reportRequestError.mockReturnValueOnce(null)
|
reportRequestError.mockReturnValueOnce(null)
|
||||||
store.dispatch(actions[c.action](c.params)).then(() => {
|
store
|
||||||
|
.dispatch(actions[c.action](c.params))
|
||||||
|
.then(() => {
|
||||||
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
expect(reportRequestError).toHaveBeenCalledTimes(1)
|
||||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
expect(reportRequestError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
error: expect.objectContaining({
|
error: expect.objectContaining({
|
||||||
errors: expect.anything(),
|
errors: expect.anything(),
|
||||||
'error_data': expect.anything()
|
error_data: expect.anything()
|
||||||
}),
|
}),
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
trimmed: ''
|
trimmed: ''
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
expect(store.getActions().length).toEqual(2)
|
expect(store.getActions().length).toEqual(2)
|
||||||
expect(store.getActions()).toEqual(expect.arrayContaining([
|
expect(store.getActions()).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: c.startAction
|
type: c.startAction
|
||||||
}),
|
}),
|
||||||
|
@ -603,10 +640,11 @@ describe('some action failure cases', () => {
|
||||||
success: false,
|
success: false,
|
||||||
message: expect.stringMatching(/[a-z]/)
|
message: expect.stringMatching(/[a-z]/)
|
||||||
})
|
})
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(e => done(e))
|
.catch((e) => done(e))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -653,10 +691,6 @@ describe('reportDetectedConflicts', () => {
|
||||||
test.todo('success')
|
test.todo('success')
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('snoozeV3DeprecationWarning', () => {
|
|
||||||
test.todo('success')
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('setConflictDetectionScanner', () => {
|
describe('setConflictDetectionScanner', () => {
|
||||||
test.todo('success when enabling')
|
test.todo('success when enabling')
|
||||||
test.todo('success when disabling')
|
test.todo('success when disabling')
|
||||||
|
@ -739,7 +773,8 @@ describe('preprocessResponse', () => {
|
||||||
|
|
||||||
actions.preprocessResponse(response)
|
actions.preprocessResponse(response)
|
||||||
|
|
||||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
expect(reportRequestError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
confirmed: false,
|
confirmed: false,
|
||||||
requestData,
|
requestData,
|
||||||
requestMethod: method,
|
requestMethod: method,
|
||||||
|
@ -749,7 +784,8 @@ describe('preprocessResponse', () => {
|
||||||
requestData: REDACTED_REQUEST_DATA,
|
requestData: REDACTED_REQUEST_DATA,
|
||||||
responseHeaders: REDACTED_HEADERS,
|
responseHeaders: REDACTED_HEADERS,
|
||||||
requestHeaders: REDACTED_HEADERS
|
requestHeaders: REDACTED_HEADERS
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -780,7 +816,8 @@ describe('preprocessResponse', () => {
|
||||||
|
|
||||||
actions.preprocessResponse(response)
|
actions.preprocessResponse(response)
|
||||||
|
|
||||||
expect(reportRequestError).toHaveBeenCalledWith(expect.objectContaining({
|
expect(reportRequestError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
confirmed: false,
|
confirmed: false,
|
||||||
requestMethod: method,
|
requestMethod: method,
|
||||||
requestUrl: url,
|
requestUrl: url,
|
||||||
|
@ -790,7 +827,8 @@ describe('preprocessResponse', () => {
|
||||||
requestData: REDACTED_REQUEST_DATA,
|
requestData: REDACTED_REQUEST_DATA,
|
||||||
responseHeaders: REDACTED_HEADERS,
|
responseHeaders: REDACTED_HEADERS,
|
||||||
requestHeaders: REDACTED_HEADERS
|
requestHeaders: REDACTED_HEADERS
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,19 +4,10 @@ import rootReducer from './reducers'
|
||||||
|
|
||||||
const middleware = [thunk]
|
const middleware = [thunk]
|
||||||
|
|
||||||
const composeEnhancers = (
|
const composeEnhancers = (process.env.NODE_ENV === 'development' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
|
||||||
process.env.NODE_ENV === 'development'
|
|
||||||
&& window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
|
||||||
) || compose
|
|
||||||
|
|
||||||
const enhancer = composeEnhancers(
|
const enhancer = composeEnhancers(applyMiddleware(...middleware))
|
||||||
applyMiddleware(...middleware)
|
|
||||||
)
|
|
||||||
|
|
||||||
export function createStore(initialData = {}) {
|
export function createStore(initialData = {}) {
|
||||||
return reduxCreateStore(
|
return reduxCreateStore(rootReducer, initialData, enhancer)
|
||||||
rootReducer,
|
|
||||||
initialData,
|
|
||||||
enhancer
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import size from 'lodash/size'
|
import { size, omit, get } from 'lodash'
|
||||||
import omit from 'lodash/omit'
|
|
||||||
import get from 'lodash/get'
|
|
||||||
import { combineReducers } from 'redux'
|
import { combineReducers } from 'redux'
|
||||||
|
|
||||||
export const ADMIN_TAB_SETTINGS = 'ADMIN_TAB_SETTINGS'
|
export const ADMIN_TAB_SETTINGS = 'ADMIN_TAB_SETTINGS'
|
||||||
export const ADMIN_TAB_TROUBLESHOOT = 'ADMIN_TAB_TROUBLESHOOT'
|
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
|
// TODO: add reducer for the clientPreferences that coerces their boolean options
|
||||||
|
|
||||||
|
@ -32,15 +30,7 @@ export function options(state = {}, action = {}) {
|
||||||
return state
|
return state
|
||||||
} else {
|
} else {
|
||||||
const {
|
const {
|
||||||
options: {
|
options: { technology, usePro, compat, pseudoElements, version, kitToken, apiToken }
|
||||||
technology,
|
|
||||||
usePro,
|
|
||||||
compat,
|
|
||||||
pseudoElements,
|
|
||||||
version,
|
|
||||||
kitToken,
|
|
||||||
apiToken
|
|
||||||
}
|
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -65,8 +55,7 @@ const OPTIONS_FORM_INITIAL_STATE = {
|
||||||
message: ''
|
message: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function optionsFormState(
|
function optionsFormState(state = OPTIONS_FORM_INITIAL_STATE, action = {}) {
|
||||||
state = OPTIONS_FORM_INITIAL_STATE, action = {}) {
|
|
||||||
const { type, success, message } = action
|
const { type, success, message } = action
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -128,9 +117,7 @@ const INITIAL_STATE_UNREGISTERED_CLIENTS_DELETION_STATUS = {
|
||||||
message: ''
|
message: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function unregisteredClientsDeletionStatus(
|
function unregisteredClientsDeletionStatus(state = INITIAL_STATE_UNREGISTERED_CLIENTS_DELETION_STATUS, action = {}) {
|
||||||
state = INITIAL_STATE_UNREGISTERED_CLIENTS_DELETION_STATUS,
|
|
||||||
action = {} ) {
|
|
||||||
const { type, success, message } = action
|
const { type, success, message } = action
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -222,7 +209,8 @@ function kitsQueryStatus(
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
message: ''
|
message: ''
|
||||||
},
|
},
|
||||||
action = {}) {
|
action = {}
|
||||||
|
) {
|
||||||
const { type, success, message } = action
|
const { type, success, message } = action
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -327,8 +315,8 @@ function unregisteredClientDetectionStatus(
|
||||||
recentConflictsDetected: {},
|
recentConflictsDetected: {},
|
||||||
message: ''
|
message: ''
|
||||||
},
|
},
|
||||||
action = {}) {
|
action = {}
|
||||||
|
) {
|
||||||
const { type, success, message, unregisteredClientsBeforeDetection, recentConflictsDetected } = action
|
const { type, success, message, unregisteredClientsBeforeDetection, recentConflictsDetected } = action
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -358,8 +346,8 @@ function conflictDetectionScannerStatus(
|
||||||
success: false,
|
success: false,
|
||||||
message: ''
|
message: ''
|
||||||
},
|
},
|
||||||
action = {}) {
|
action = {}
|
||||||
|
) {
|
||||||
const { type, success, message } = action
|
const { type, success, message } = action
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -374,37 +362,6 @@ 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 = {}) {
|
function showConflictDetectionReporter(state = false, action = {}) {
|
||||||
const { type } = action
|
const { type } = action
|
||||||
|
|
||||||
|
@ -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({
|
export default combineReducers({
|
||||||
activeAdminTab,
|
activeAdminTab,
|
||||||
apiNonce: simple,
|
apiNonce: simple,
|
||||||
apiUrl: simple,
|
apiUrl: simple,
|
||||||
|
faApiUrl: simple,
|
||||||
blocklistUpdateStatus,
|
blocklistUpdateStatus,
|
||||||
clientPreferences: coerceEmptyArrayToEmptyObject,
|
clientPreferences: coerceEmptyArrayToEmptyObject,
|
||||||
conflictDetectionScannerStatus,
|
conflictDetectionScannerStatus,
|
||||||
|
@ -472,7 +432,6 @@ export default combineReducers({
|
||||||
rootUrl: simple,
|
rootUrl: simple,
|
||||||
mainCdnAssetUrl: simple,
|
mainCdnAssetUrl: simple,
|
||||||
mainCdnAssetIntegrity: simple,
|
mainCdnAssetIntegrity: simple,
|
||||||
enableIconChooser: coerceBool,
|
|
||||||
releases: simple,
|
releases: simple,
|
||||||
settingsPageUrl: simple,
|
settingsPageUrl: simple,
|
||||||
showAdmin: coerceBool,
|
showAdmin: coerceBool,
|
||||||
|
@ -481,9 +440,5 @@ export default combineReducers({
|
||||||
unregisteredClients,
|
unregisteredClients,
|
||||||
unregisteredClientsDeletionStatus,
|
unregisteredClientsDeletionStatus,
|
||||||
userAttemptedToStopScanner,
|
userAttemptedToStopScanner,
|
||||||
v3DeprecationWarning,
|
webpackPublicPath: simple
|
||||||
v3DeprecationWarningStatus,
|
|
||||||
webpackPublicPath: simple,
|
|
||||||
isGutenbergPage: coerceBool,
|
|
||||||
usingCompatJs: coerceBool
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe('options', () => {
|
||||||
usePro: true,
|
usePro: true,
|
||||||
compat: false,
|
compat: false,
|
||||||
pseudoElements: true,
|
pseudoElements: true,
|
||||||
version: '5.11.2',
|
version: '5.11.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ export async function resetOptions(page) {
|
||||||
options: {
|
options: {
|
||||||
usePro: false,
|
usePro: false,
|
||||||
compat: true,
|
compat: true,
|
||||||
technology:"webfont",
|
technology: 'webfont',
|
||||||
pseudoElements: true,
|
pseudoElements: true,
|
||||||
kitToken: null,
|
kitToken: null,
|
||||||
apiToken: true,
|
apiToken: true,
|
||||||
version:"6.0.0-beta3"
|
version: '6.0.0-beta3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,40 @@
|
||||||
import get from 'lodash/get'
|
import { get, set, size } from 'lodash'
|
||||||
import set from 'lodash/set'
|
|
||||||
import size from 'lodash/size'
|
|
||||||
import { __ } from '@wordpress/i18n'
|
import { __ } from '@wordpress/i18n'
|
||||||
|
|
||||||
export const ERROR_REPORT_PREAMBLE = __('Font Awesome WordPress Plugin Error Report', 'font-awesome')
|
export const ERROR_REPORT_PREAMBLE = __('Font Awesome WordPress Plugin Error Report', 'font-awesome')
|
||||||
const UI_MESSAGE_DEFAULT = __( 'D\'oh! That failed big time.', 'font-awesome' )
|
const UI_MESSAGE_DEFAULT = __("D'oh! That failed big time.", 'font-awesome')
|
||||||
const ERROR_REPORTING_ERROR = __('There was an error attempting to report the error.', 'font-awesome')
|
const ERROR_REPORTING_ERROR = __('There was an error attempting to report the error.', 'font-awesome')
|
||||||
const REST_NO_ROUTE_ERROR = __('Oh no! Your web browser could not reach your WordPress server.', 'font-awesome')
|
const REST_NO_ROUTE_ERROR = __('Oh no! Your web browser could not reach your WordPress server.', 'font-awesome')
|
||||||
const REST_COOKIE_INVALID_NONCE_ERROR = __( 'It looks like your web browser session expired. Try logging out and log back in to WordPress admin.', 'font-awesome' )
|
const REST_COOKIE_INVALID_NONCE_ERROR = __(
|
||||||
const OK_ERROR_PREAMBLE = __( 'The last request was successful, but it also returned the following error(s), which might be helpful for troubleshooting.', 'font-awesome' )
|
'It looks like your web browser session expired. Try logging out and log back in to WordPress admin.',
|
||||||
|
'font-awesome'
|
||||||
|
)
|
||||||
|
const OK_ERROR_PREAMBLE = __(
|
||||||
|
'The last request was successful, but it also returned the following error(s), which might be helpful for troubleshooting.',
|
||||||
|
'font-awesome'
|
||||||
|
)
|
||||||
const ONE_OF_MANY_ERRORS_GROUP_LABEL = __('Error', 'font-awesome')
|
const ONE_OF_MANY_ERRORS_GROUP_LABEL = __('Error', 'font-awesome')
|
||||||
const FALSE_POSITIVE_MESSAGE = __( 'WARNING: The last request contained errors, though your WordPress server reported it as a success. This usually means there\'s a problem with your theme or one of your other plugins emitting output that is causing problems.', 'font-awesome' )
|
const FALSE_POSITIVE_MESSAGE = __(
|
||||||
const UNCONFIRMED_RESPONSE_MESSAGE = __( 'WARNING: The last response from your WordPress server did not include the confirmation header that should be in all valid Font Awesome responses. This is a clue that some code from another theme or plugin is acting badly and causing the wrong headers to be sent.', 'font-awesome')
|
"WARNING: The last request contained errors, though your WordPress server reported it as a success. This usually means there's a problem with your theme or one of your other plugins emitting output that is causing problems.",
|
||||||
const CONFIRMED_RESPONSE_MESSAGE = __( 'CONFIRMED: The last response from your WordPress server included the confirmation header that is expected for all valid responses from the Font Awesome plugin\'s code running on your WordPress server.', 'font-awesome')
|
'font-awesome'
|
||||||
|
)
|
||||||
|
const UNCONFIRMED_RESPONSE_MESSAGE = __(
|
||||||
|
'WARNING: The last response from your WordPress server did not include the confirmation header that should be in all valid Font Awesome responses. This is a clue that some code from another theme or plugin is acting badly and causing the wrong headers to be sent.',
|
||||||
|
'font-awesome'
|
||||||
|
)
|
||||||
|
const CONFIRMED_RESPONSE_MESSAGE = __(
|
||||||
|
"CONFIRMED: The last response from your WordPress server included the confirmation header that is expected for all valid responses from the Font Awesome plugin's code running on your WordPress server.",
|
||||||
|
'font-awesome'
|
||||||
|
)
|
||||||
const TRIMMED_RESPONSE_PREAMBLE = __('WARNING: Invalid Data Trimmed from Server Response', 'font-awesome')
|
const TRIMMED_RESPONSE_PREAMBLE = __('WARNING: Invalid Data Trimmed from Server Response', 'font-awesome')
|
||||||
const EXPECTED_EMPTY_MESSAGE = __( 'WARNING: We expected the last response from the server to contain no data, but it contained something unexpected.', 'font-awesome' )
|
const EXPECTED_EMPTY_MESSAGE = __(
|
||||||
const MISSING_ERROR_DATA_MESSAGE = __( 'Your WordPress server returned an error for that last request, but there was no information about the error.', 'font-awesome' )
|
'WARNING: We expected the last response from the server to contain no data, but it contained something unexpected.',
|
||||||
|
'font-awesome'
|
||||||
|
)
|
||||||
|
const MISSING_ERROR_DATA_MESSAGE = __(
|
||||||
|
'Your WordPress server returned an error for that last request, but there was no information about the error.',
|
||||||
|
'font-awesome'
|
||||||
|
)
|
||||||
const REPORT_INFO_PARAM_KEYS = [
|
const REPORT_INFO_PARAM_KEYS = [
|
||||||
'requestMethod',
|
'requestMethod',
|
||||||
'responseStatus',
|
'responseStatus',
|
||||||
|
@ -97,7 +116,7 @@ function handleSingleWpErrorOutput( wpError ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAllWpErrorOutput(errorData = {}) {
|
function handleAllWpErrorOutput(errorData = {}) {
|
||||||
const wpErrors = Object.keys(errorData.errors || []).map(code => {
|
const wpErrors = Object.keys(errorData.errors || []).map((code) => {
|
||||||
// get the first error message available for this code
|
// get the first error message available for this code
|
||||||
const message = get(errorData, `errors.${code}.0`)
|
const message = get(errorData, `errors.${code}.0`)
|
||||||
const data = get(errorData, `error_data.${code}`)
|
const data = get(errorData, `error_data.${code}`)
|
||||||
|
@ -125,23 +144,14 @@ function handleAllWpErrorOutput(errorData = {}) {
|
||||||
|
|
||||||
// The uiMessage we should return will be the first error message that isn't
|
// The uiMessage we should return will be the first error message that isn't
|
||||||
// from a 'previous_exception'
|
// from a 'previous_exception'
|
||||||
return (!acc && error.code !== 'previous_exception')
|
return !acc && error.code !== 'previous_exception' ? msg : acc
|
||||||
? msg
|
|
||||||
: acc
|
|
||||||
}, null)
|
}, null)
|
||||||
|
|
||||||
return uiMessage
|
return uiMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
function report(params) {
|
function report(params) {
|
||||||
const {
|
const { error = null, ok = false, falsePositive = false, confirmed = false, expectEmpty = false, trimmed = '' } = params
|
||||||
error = null,
|
|
||||||
ok = false,
|
|
||||||
falsePositive = false,
|
|
||||||
confirmed = false,
|
|
||||||
expectEmpty = false,
|
|
||||||
trimmed = ''
|
|
||||||
} = params
|
|
||||||
|
|
||||||
console.group(ERROR_REPORT_PREAMBLE)
|
console.group(ERROR_REPORT_PREAMBLE)
|
||||||
|
|
||||||
|
@ -195,9 +205,7 @@ function report(params) {
|
||||||
console.groupEnd()
|
console.groupEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
const uiMessage = null !== error
|
const uiMessage = null !== error ? handleAllWpErrorOutput(error) : null
|
||||||
? handleAllWpErrorOutput( error )
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (error && trimmed === '' && confirmed) {
|
if (error && trimmed === '' && confirmed) {
|
||||||
console.info(MISSING_ERROR_DATA_MESSAGE)
|
console.info(MISSING_ERROR_DATA_MESSAGE)
|
||||||
|
|
|
@ -6,12 +6,13 @@ console.info = jest.fn()
|
||||||
|
|
||||||
const SINGLE_EXCEPTION_ERROR = {
|
const SINGLE_EXCEPTION_ERROR = {
|
||||||
errors: {
|
errors: {
|
||||||
fontawesome_client_exception: ["Whoops, it looks like that API Token is not valid. Try another one?"]
|
fontawesome_client_exception: ['Whoops, it looks like that API Token is not valid. Try another one?']
|
||||||
},
|
},
|
||||||
error_data: {
|
error_data: {
|
||||||
fontawesome_client_exception: {
|
fontawesome_client_exception: {
|
||||||
status: 400,
|
status: 400,
|
||||||
trace:"#0 \/var\/www\/html\/wp-content\/plugins\/font-awesome\/includes\/class-fontawesome-api-settings.php(311): FortAwesome\\FontAwesome_Exception::with_wp_response(Array)\n#1 \/var\/www\/html\/wp-content\/plugins\/font-awesome\/includes\/class-fontawesome-config-controller.php(115): FortAwesome\\FontAwesome_API_Settings->request_access_token()\n#2 \/var\/www\/html\/wp-includes\/rest-api\/class-wp-rest-server.php(946): FortAwesome\\FontAwesome_Config_Controller->update_item(Object(WP_REST_Request))\n#3 \/var\/www\/html\/wp-includes\/rest-api\/class-wp-rest-server.php(329): WP_REST_Server->dispatch(Object(WP_REST_Request))\n#4 \/var\/www\/html\/wp-includes\/rest-api.php(305): WP_REST_Server->serve_request('\/font-awesome\/v...')\n#5 \/var\/www\/html\/wp-includes\/class-wp-hook.php(288): rest_api_loaded(Object(WP))\n#6 \/var\/www\/html\/wp-includes\/class-wp-hook.php(312): WP_Hook->apply_filters('', Array)\n#7 \/var\/www\/html\/wp-includes\/plugin.php(544): WP_Hook->do_action(Array)\n#8 \/var\/www\/html\/wp-includes\/class-wp.php(387): do_action_ref_array('parse_request', Array)\n#9 \/var\/www\/html\/wp-includes\/class-wp.php(729): WP->parse_request('')\n#10 \/var\/www\/html\/wp-includes\/functions.php(1255): WP->main('')\n#11 \/var\/www\/html\/wp-blog-header.php(16): wp()\n#12 \/var\/www\/html\/index.php(17): require('\/var\/www\/html\/w...')\n#13 {main}"
|
trace:
|
||||||
|
"#0 /var/www/html/wp-content/plugins/font-awesome/includes/class-fontawesome-api-settings.php(311): FortAwesome\\FontAwesome_Exception::with_wp_response(Array)\n#1 /var/www/html/wp-content/plugins/font-awesome/includes/class-fontawesome-config-controller.php(115): FortAwesome\\FontAwesome_API_Settings->request_access_token()\n#2 /var/www/html/wp-includes/rest-api/class-wp-rest-server.php(946): FortAwesome\\FontAwesome_Config_Controller->update_item(Object(WP_REST_Request))\n#3 /var/www/html/wp-includes/rest-api/class-wp-rest-server.php(329): WP_REST_Server->dispatch(Object(WP_REST_Request))\n#4 /var/www/html/wp-includes/rest-api.php(305): WP_REST_Server->serve_request('/font-awesome/v...')\n#5 /var/www/html/wp-includes/class-wp-hook.php(288): rest_api_loaded(Object(WP))\n#6 /var/www/html/wp-includes/class-wp-hook.php(312): WP_Hook->apply_filters('', Array)\n#7 /var/www/html/wp-includes/plugin.php(544): WP_Hook->do_action(Array)\n#8 /var/www/html/wp-includes/class-wp.php(387): do_action_ref_array('parse_request', Array)\n#9 /var/www/html/wp-includes/class-wp.php(729): WP->parse_request('')\n#10 /var/www/html/wp-includes/functions.php(1255): WP->main('')\n#11 /var/www/html/wp-blog-header.php(16): wp()\n#12 /var/www/html/index.php(17): require('/var/www/html/w...')\n#13 {main}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +33,6 @@ describe('reportRequestError', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with single fontawesome_client_exception', () => {
|
describe('with single fontawesome_client_exception', () => {
|
||||||
|
|
||||||
test('emits console report and returns uiMessage from given error', () => {
|
test('emits console report and returns uiMessage from given error', () => {
|
||||||
const message = reportRequestError({ error: SINGLE_EXCEPTION_ERROR })
|
const message = reportRequestError({ error: SINGLE_EXCEPTION_ERROR })
|
||||||
|
|
||||||
|
@ -41,20 +41,12 @@ describe('reportRequestError', () => {
|
||||||
expect(console.group).toHaveBeenCalledTimes(2)
|
expect(console.group).toHaveBeenCalledTimes(2)
|
||||||
expect(console.info).toHaveBeenCalled()
|
expect(console.info).toHaveBeenCalled()
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/message: Whoops/))
|
||||||
expect.stringMatching(/message: Whoops/),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/trace:/))
|
||||||
expect.stringMatching(/trace:/)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/status:/))
|
||||||
expect.stringMatching(/status:/),
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/code: fontawesome_client_exception/))
|
||||||
)
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
|
||||||
expect.stringMatching(/code: fontawesome_client_exception/)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.groupEnd).toHaveBeenCalledTimes(2)
|
expect(console.groupEnd).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
@ -63,17 +55,19 @@ describe('reportRequestError', () => {
|
||||||
describe('when PreferenceRegistrationException is thrown with a previous exception', () => {
|
describe('when PreferenceRegistrationException is thrown with a previous exception', () => {
|
||||||
const error = {
|
const error = {
|
||||||
errors: {
|
errors: {
|
||||||
fontawesome_server_exception: ["A theme or plugin registered with Font Awesome threw an exception."],
|
fontawesome_server_exception: ['A theme or plugin registered with Font Awesome threw an exception.'],
|
||||||
previous_exception: ["epsilon-plugin throwing"]
|
previous_exception: ['epsilon-plugin throwing']
|
||||||
},
|
},
|
||||||
error_data: {
|
error_data: {
|
||||||
fontawesome_server_exception: {
|
fontawesome_server_exception: {
|
||||||
status: 500,
|
status: 500,
|
||||||
trace:"#0 \/var\/www\/html\/wp-content\/plugins\/font-awesome\/includes\/class-fontawesome.php(1057): FortAwesome\\FontAwesome_Exception::with_thrown(Object(Exception))\n#1 \/var\/www\/html\/wp-content\/plugins\/font-awesome\/includes\/class-fontawesome-config-controller.php(80): FortAwesome\\FontAwesome->gather_preferences()\n#2 \/var\/www\/html\/wp-content\/plugins\/font-awesome\/includes\/class-fontawesome-config-controller.php(134): FortAwesome\\FontAwesome_Config_Controller->build_item(Object(FortAwesome\\FontAwesome))\n#3 \/var\/www\/html\/wp-includes\/rest-api\/class-wp-rest-server.php(946): FortAwesome\\FontAwesome_Config_Controller->update_item(Object(WP_REST_Request))\n#4 \/var\/www\/html\/wp-includes\/rest-api\/class-wp-rest-server.php(329): WP_REST_Server->dispatch(Object(WP_REST_Request))\n#5 \/var\/www\/html\/wp-includes\/rest-api.php(305): WP_REST_Server->serve_request('\/font-awesome\/v...')\n#6 \/var\/www\/html\/wp-includes\/class-wp-hook.php(288): rest_api_loaded(Object(WP))\n#7 \/var\/www\/html\/wp-includes\/class-wp-hook.php(312): WP_Hook->apply_filters('', Array)\n#8 \/var\/www\/html\/wp-includes\/plugin.php(544): WP_Hook->do_action(Array)\n#9 \/var\/www\/html\/wp-includes\/class-wp.php(387): do_action_ref_array('parse_request', Array)\n#10 \/var\/www\/html\/wp-includes\/class-wp.php(729): WP->parse_request('')\n#11 \/var\/www\/html\/wp-includes\/functions.php(1255): WP->main('')\n#12 \/var\/www\/html\/wp-blog-header.php(16): wp()\n#13 \/var\/www\/html\/index.php(17): require('\/var\/www\/html\/w...')\n#14 {main}"
|
trace:
|
||||||
|
"#0 /var/www/html/wp-content/plugins/font-awesome/includes/class-fontawesome.php(1057): FortAwesome\\FontAwesome_Exception::with_thrown(Object(Exception))\n#1 /var/www/html/wp-content/plugins/font-awesome/includes/class-fontawesome-config-controller.php(80): FortAwesome\\FontAwesome->gather_preferences()\n#2 /var/www/html/wp-content/plugins/font-awesome/includes/class-fontawesome-config-controller.php(134): FortAwesome\\FontAwesome_Config_Controller->build_item(Object(FortAwesome\\FontAwesome))\n#3 /var/www/html/wp-includes/rest-api/class-wp-rest-server.php(946): FortAwesome\\FontAwesome_Config_Controller->update_item(Object(WP_REST_Request))\n#4 /var/www/html/wp-includes/rest-api/class-wp-rest-server.php(329): WP_REST_Server->dispatch(Object(WP_REST_Request))\n#5 /var/www/html/wp-includes/rest-api.php(305): WP_REST_Server->serve_request('/font-awesome/v...')\n#6 /var/www/html/wp-includes/class-wp-hook.php(288): rest_api_loaded(Object(WP))\n#7 /var/www/html/wp-includes/class-wp-hook.php(312): WP_Hook->apply_filters('', Array)\n#8 /var/www/html/wp-includes/plugin.php(544): WP_Hook->do_action(Array)\n#9 /var/www/html/wp-includes/class-wp.php(387): do_action_ref_array('parse_request', Array)\n#10 /var/www/html/wp-includes/class-wp.php(729): WP->parse_request('')\n#11 /var/www/html/wp-includes/functions.php(1255): WP->main('')\n#12 /var/www/html/wp-blog-header.php(16): wp()\n#13 /var/www/html/index.php(17): require('/var/www/html/w...')\n#14 {main}"
|
||||||
},
|
},
|
||||||
previous_exception: {
|
previous_exception: {
|
||||||
status: 500,
|
status: 500,
|
||||||
trace: "#0 \/var\/www\/html\/wp-includes\/class-wp-hook.php(288): {closure}('')\n#1 \/var\/www\/html\/wp-includes\/class-wp-hook.php(312): WP_Hook->apply_filters('', Array)\n#2 \/var\/www\/html\/wp-includes\/plugin.php(478): WP_Hook->do_action(Array)\n#3 \/var\/www\/html\/wp-content\/plugins\/font-awesome\/includes\/class-fontawesome.php(1055): do_action('font_awesome_pr...')\n#4 \/var\/www\/html\/wp-content\/plugins\/font-awesome\/includes\/class-fontawesome-config-controller.php(80): FortAwesome\\FontAwesome->gather_preferences()\n#5 \/var\/www\/html\/wp-content\/plugins\/font-awesome\/includes\/class-fontawesome-config-controller.php(134): FortAwesome\\FontAwesome_Config_Controller->build_item(Object(FortAwesome\\FontAwesome))\n#6 \/var\/www\/html\/wp-includes\/rest-api\/class-wp-rest-server.php(946): FortAwesome\\FontAwesome_Config_Controller->update_item(Object(WP_REST_Request))\n#7 \/var\/www\/html\/wp-includes\/rest-api\/class-wp-rest-server.php(329): WP_REST_Server->dispatch(Object(WP_REST_Request))\n#8 \/var\/www\/html\/wp-includes\/rest-api.php(305): WP_REST_Server->serve_request('\/font-awesome\/v...')\n#9 \/var\/www\/html\/wp-includes\/class-wp-hook.php(288): rest_api_loaded(Object(WP))\n#10 \/var\/www\/html\/wp-includes\/class-wp-hook.php(312): WP_Hook->apply_filters('', Array)\n#11 \/var\/www\/html\/wp-includes\/plugin.php(544): WP_Hook->do_action(Array)\n#12 \/var\/www\/html\/wp-includes\/class-wp.php(387): do_action_ref_array('parse_request', Array)\n#13 \/var\/www\/html\/wp-includes\/class-wp.php(729): WP->parse_request('')\n#14 \/var\/www\/html\/wp-includes\/functions.php(1255): WP->main('')\n#15 \/var\/www\/html\/wp-blog-header.php(16): wp()\n#16 \/var\/www\/html\/index.php(17): require('\/var\/www\/html\/w...')\n#17 {main}"
|
trace:
|
||||||
|
"#0 /var/www/html/wp-includes/class-wp-hook.php(288): {closure}('')\n#1 /var/www/html/wp-includes/class-wp-hook.php(312): WP_Hook->apply_filters('', Array)\n#2 /var/www/html/wp-includes/plugin.php(478): WP_Hook->do_action(Array)\n#3 /var/www/html/wp-content/plugins/font-awesome/includes/class-fontawesome.php(1055): do_action('font_awesome_pr...')\n#4 /var/www/html/wp-content/plugins/font-awesome/includes/class-fontawesome-config-controller.php(80): FortAwesome\\FontAwesome->gather_preferences()\n#5 /var/www/html/wp-content/plugins/font-awesome/includes/class-fontawesome-config-controller.php(134): FortAwesome\\FontAwesome_Config_Controller->build_item(Object(FortAwesome\\FontAwesome))\n#6 /var/www/html/wp-includes/rest-api/class-wp-rest-server.php(946): FortAwesome\\FontAwesome_Config_Controller->update_item(Object(WP_REST_Request))\n#7 /var/www/html/wp-includes/rest-api/class-wp-rest-server.php(329): WP_REST_Server->dispatch(Object(WP_REST_Request))\n#8 /var/www/html/wp-includes/rest-api.php(305): WP_REST_Server->serve_request('/font-awesome/v...')\n#9 /var/www/html/wp-includes/class-wp-hook.php(288): rest_api_loaded(Object(WP))\n#10 /var/www/html/wp-includes/class-wp-hook.php(312): WP_Hook->apply_filters('', Array)\n#11 /var/www/html/wp-includes/plugin.php(544): WP_Hook->do_action(Array)\n#12 /var/www/html/wp-includes/class-wp.php(387): do_action_ref_array('parse_request', Array)\n#13 /var/www/html/wp-includes/class-wp.php(729): WP->parse_request('')\n#14 /var/www/html/wp-includes/functions.php(1255): WP->main('')\n#15 /var/www/html/wp-blog-header.php(16): wp()\n#16 /var/www/html/index.php(17): require('/var/www/html/w...')\n#17 {main}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,17 +78,11 @@ describe('reportRequestError', () => {
|
||||||
expect(console.group).toHaveBeenCalledTimes(3)
|
expect(console.group).toHaveBeenCalledTimes(3)
|
||||||
expect(console.groupEnd).toHaveBeenCalledTimes(3)
|
expect(console.groupEnd).toHaveBeenCalledTimes(3)
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/code: fontawesome_server_exception/))
|
||||||
expect.stringMatching(/code: fontawesome_server_exception/)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/code: previous_exception/))
|
||||||
expect.stringMatching(/code: previous_exception/)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/The last request was successful/))
|
||||||
expect.stringMatching(/The last request was successful/)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(message).toMatch(/^A theme or plugin/)
|
expect(message).toMatch(/^A theme or plugin/)
|
||||||
})
|
})
|
||||||
|
@ -114,48 +102,28 @@ describe('reportRequestError', () => {
|
||||||
expect(message).toMatch(/^Whoops/)
|
expect(message).toMatch(/^Whoops/)
|
||||||
// The top-level group, and then one error group, then one for the trimmed content
|
// The top-level group, and then one error group, then one for the trimmed content
|
||||||
expect(console.group).toHaveBeenCalledTimes(3)
|
expect(console.group).toHaveBeenCalledTimes(3)
|
||||||
expect(console.group).toHaveBeenCalledWith(
|
expect(console.group).toHaveBeenCalledWith(expect.stringMatching(/Error Report/))
|
||||||
expect.stringMatching(/Error Report/),
|
expect(console.group).toHaveBeenCalledWith(expect.stringMatching(/Trimmed/))
|
||||||
)
|
|
||||||
expect(console.group).toHaveBeenCalledWith(
|
|
||||||
expect.stringMatching(/Trimmed/),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.groupEnd).toHaveBeenCalledTimes(3)
|
expect(console.groupEnd).toHaveBeenCalledTimes(3)
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledTimes(4)
|
expect(console.info).toHaveBeenCalledTimes(4)
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/reported it as a success/))
|
||||||
expect.stringMatching(/reported it as a success/),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/This is a clue/))
|
||||||
expect.stringMatching(/This is a clue/),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/message: Whoops/))
|
||||||
expect.stringMatching(/message: Whoops/),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/message: Whoops/))
|
||||||
expect.stringMatching(/message: Whoops/),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/trace:/))
|
||||||
expect.stringMatching(/trace:/)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/status:/))
|
||||||
expect.stringMatching(/status:/),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/code: fontawesome_client_exception/))
|
||||||
expect.stringMatching(/code: fontawesome_client_exception/)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringContaining(TRIMMED))
|
||||||
expect.stringContaining(TRIMMED)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -172,21 +140,15 @@ describe('reportRequestError', () => {
|
||||||
expect(message).toBeNull()
|
expect(message).toBeNull()
|
||||||
// The top-level group, and then one for the trimmed content
|
// The top-level group, and then one for the trimmed content
|
||||||
expect(console.group).toHaveBeenCalledTimes(2)
|
expect(console.group).toHaveBeenCalledTimes(2)
|
||||||
expect(console.group).toHaveBeenCalledWith(
|
expect(console.group).toHaveBeenCalledWith(expect.stringMatching(/Trimmed/))
|
||||||
expect.stringMatching(/Trimmed/),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.groupEnd).toHaveBeenCalledTimes(2)
|
expect(console.groupEnd).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalled()
|
expect(console.info).toHaveBeenCalled()
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/contain no data/))
|
||||||
expect.stringMatching(/contain no data/),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringContaining(TRIMMED))
|
||||||
expect.stringContaining(TRIMMED),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -203,9 +165,7 @@ describe('reportRequestError', () => {
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledTimes(1)
|
expect(console.info).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/did not include the confirmation header/))
|
||||||
expect.stringMatching(/did not include the confirmation header/),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -257,9 +217,7 @@ describe('reportRequestError', () => {
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalled()
|
expect(console.info).toHaveBeenCalled()
|
||||||
|
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(console.info).toHaveBeenCalledWith(expect.stringMatching(/failure console message/))
|
||||||
expect.stringMatching(/failure console message/)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -273,7 +231,7 @@ describe('redactRequestData', () => {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
data: JSON.stringify({ options: { foo: 42, apiToken: 'abc123' } })
|
data: JSON.stringify({ options: { foo: 42, apiToken: 'abc123' } })
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(redactRequestData(response)).toEqual(JSON.stringify({ options: { foo: 42, apiToken: 'REDACTED' } }))
|
expect(redactRequestData(response)).toEqual(JSON.stringify({ options: { foo: 42, apiToken: 'REDACTED' } }))
|
||||||
|
@ -288,7 +246,7 @@ describe('redactRequestData', () => {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
data: JSON.stringify({ options: { foo: 42, apiToken: true } })
|
data: JSON.stringify({ options: { foo: 42, apiToken: true } })
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(redactRequestData(response)).toEqual(JSON.stringify({ options: { foo: 42, apiToken: true } }))
|
expect(redactRequestData(response)).toEqual(JSON.stringify({ options: { foo: 42, apiToken: true } }))
|
||||||
|
@ -303,7 +261,7 @@ describe('redactRequestData', () => {
|
||||||
'Content-Type': 'text/plain'
|
'Content-Type': 'text/plain'
|
||||||
},
|
},
|
||||||
data: JSON.stringify({ options: { foo: 42, beta: 43 } })
|
data: JSON.stringify({ options: { foo: 42, beta: 43 } })
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(redactRequestData(response)).toEqual(JSON.stringify({ options: { foo: 42, beta: 43 } }))
|
expect(redactRequestData(response)).toEqual(JSON.stringify({ options: { foo: 42, beta: 43 } }))
|
||||||
|
@ -313,14 +271,20 @@ describe('redactRequestData', () => {
|
||||||
|
|
||||||
describe('redactHeaders', () => {
|
describe('redactHeaders', () => {
|
||||||
test('when x-wp-nonce is present', () => {
|
test('when x-wp-nonce is present', () => {
|
||||||
expect(redactHeaders({
|
expect(
|
||||||
|
redactHeaders({
|
||||||
'X-WP-NONCE': 'abc123'
|
'X-WP-NONCE': 'abc123'
|
||||||
})).toEqual({'X-WP-NONCE': 'REDACTED'})
|
})
|
||||||
expect(redactHeaders({
|
).toEqual({ 'X-WP-NONCE': 'REDACTED' })
|
||||||
|
expect(
|
||||||
|
redactHeaders({
|
||||||
'x-wp-nonce': 'abc123'
|
'x-wp-nonce': 'abc123'
|
||||||
})).toEqual({'x-wp-nonce': 'REDACTED'})
|
})
|
||||||
expect(redactHeaders({
|
).toEqual({ 'x-wp-nonce': 'REDACTED' })
|
||||||
|
expect(
|
||||||
|
redactHeaders({
|
||||||
'X-WP-Nonce': 'abc123'
|
'X-WP-Nonce': 'abc123'
|
||||||
})).toEqual({'X-WP-Nonce': 'REDACTED'})
|
})
|
||||||
|
).toEqual({ 'X-WP-Nonce': 'REDACTED' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,9 +22,7 @@ function findJson( content, start = 0 ) {
|
||||||
} else {
|
} else {
|
||||||
if (-1 !== nextLeftBracket && -1 !== nextLeftBrace) {
|
if (-1 !== nextLeftBracket && -1 !== nextLeftBrace) {
|
||||||
// if we found both, take the lower one
|
// if we found both, take the lower one
|
||||||
nextStart = nextLeftBracket < nextLeftBrace
|
nextStart = nextLeftBracket < nextLeftBrace ? nextLeftBracket : nextLeftBrace
|
||||||
? nextLeftBracket
|
|
||||||
: nextLeftBrace
|
|
||||||
} else if (-1 !== nextLeftBrace) {
|
} else if (-1 !== nextLeftBrace) {
|
||||||
nextStart = nextLeftBrace
|
nextStart = nextLeftBrace
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,12 +5,14 @@ describe('sliceJson', () => {
|
||||||
const json = '{"alpha":42}'
|
const json = '{"alpha":42}'
|
||||||
const result = sliceJson(json)
|
const result = sliceJson(json)
|
||||||
|
|
||||||
expect(result).toEqual( expect.objectContaining({
|
expect(result).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
start: 0,
|
start: 0,
|
||||||
json,
|
json,
|
||||||
trimmed: '',
|
trimmed: '',
|
||||||
parsed: expect.objectContaining(JSON.parse(json))
|
parsed: expect.objectContaining(JSON.parse(json))
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('when invalid content precedes json object', () => {
|
test('when invalid content precedes json object', () => {
|
||||||
|
@ -19,12 +21,14 @@ describe('sliceJson', () => {
|
||||||
const content = `${trimmed}${json}`
|
const content = `${trimmed}${json}`
|
||||||
const result = sliceJson(content)
|
const result = sliceJson(content)
|
||||||
|
|
||||||
expect(result).toEqual( expect.objectContaining({
|
expect(result).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
start: 7,
|
start: 7,
|
||||||
json,
|
json,
|
||||||
trimmed,
|
trimmed,
|
||||||
parsed: expect.objectContaining(JSON.parse(json))
|
parsed: expect.objectContaining(JSON.parse(json))
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('when invalid content precedes json array', () => {
|
test('when invalid content precedes json array', () => {
|
||||||
|
@ -33,12 +37,14 @@ describe('sliceJson', () => {
|
||||||
const content = `${trimmed}${json}`
|
const content = `${trimmed}${json}`
|
||||||
const result = sliceJson(content)
|
const result = sliceJson(content)
|
||||||
|
|
||||||
expect(result).toEqual( expect.objectContaining({
|
expect(result).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
start: 7,
|
start: 7,
|
||||||
json,
|
json,
|
||||||
trimmed,
|
trimmed,
|
||||||
parsed: expect.objectContaining(JSON.parse(json))
|
parsed: expect.objectContaining(JSON.parse(json))
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('when invalid content with brackets and braces precedes json object', () => {
|
test('when invalid content with brackets and braces precedes json object', () => {
|
||||||
|
@ -47,12 +53,14 @@ describe('sliceJson', () => {
|
||||||
const content = `${trimmed}${json}`
|
const content = `${trimmed}${json}`
|
||||||
const result = sliceJson(content)
|
const result = sliceJson(content)
|
||||||
|
|
||||||
expect(result).toEqual( expect.objectContaining({
|
expect(result).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
start: 20,
|
start: 20,
|
||||||
json,
|
json,
|
||||||
trimmed,
|
trimmed,
|
||||||
parsed: expect.objectContaining(JSON.parse(json))
|
parsed: expect.objectContaining(JSON.parse(json))
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('when invalid content with brackets and braces precedes json array', () => {
|
test('when invalid content with brackets and braces precedes json array', () => {
|
||||||
|
@ -61,12 +69,14 @@ describe('sliceJson', () => {
|
||||||
const content = `${trimmed}${json}`
|
const content = `${trimmed}${json}`
|
||||||
const result = sliceJson(content)
|
const result = sliceJson(content)
|
||||||
|
|
||||||
expect(result).toEqual( expect.objectContaining({
|
expect(result).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
start: 20,
|
start: 20,
|
||||||
json,
|
json,
|
||||||
trimmed,
|
trimmed,
|
||||||
parsed: expect.objectContaining(JSON.parse(json))
|
parsed: expect.objectContaining(JSON.parse(json))
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('when invalid content comes before and after valid json', () => {
|
test('when invalid content comes before and after valid json', () => {
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
const defaultConfig = require("@wordpress/scripts/config/webpack.config");
|
const defaultConfig = require('@wordpress/scripts/config/webpack.config')
|
||||||
const get = require("lodash/get");
|
const get = require('lodash/get')
|
||||||
const { basename, dirname } = require("path");
|
const { basename, dirname } = require('path')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There have apparently been problems with webpack chunk loading and caches.
|
* There have apparently been problems with webpack chunk loading and caches.
|
||||||
* So we should add hashes to the chunk file names.
|
* So we should add hashes to the chunk file names.
|
||||||
* See: https://wordpress.org/support/topic/plugin-settings-page-is-empty-2/#post-14855904
|
* See: https://wordpress.org/support/topic/plugin-settings-page-is-empty-2/#post-14855904
|
||||||
*/
|
*/
|
||||||
const miniCssExtractPlugin = defaultConfig.plugins.find((p) =>
|
const miniCssExtractPlugin = defaultConfig.plugins.find((p) => 'MiniCssExtractPlugin' === p.constructor.name)
|
||||||
"MiniCssExtractPlugin" === p.constructor.name
|
miniCssExtractPlugin.options.filename = '[name]-[contenthash].css'
|
||||||
);
|
miniCssExtractPlugin.options.chunkFilename = '[name]-[chunkhash].css'
|
||||||
miniCssExtractPlugin.options.filename = "[name]-[contenthash].css";
|
|
||||||
miniCssExtractPlugin.options.chunkFilename = "[name]-[chunkhash].css";
|
|
||||||
|
|
||||||
// After updating to @wordpress/scripts wp-6.5, when using --webpack-no-externals
|
// After updating to @wordpress/scripts wp-6.5, when using --webpack-no-externals
|
||||||
// there was an error:
|
// there was an error:
|
||||||
|
@ -35,112 +33,13 @@ miniCssExtractPlugin.options.chunkFilename = "[name]-[chunkhash].css";
|
||||||
//
|
//
|
||||||
// Using the empty string here instead of null seems to result in that asset still
|
// Using the empty string here instead of null seems to result in that asset still
|
||||||
// loading as expected and styling everything as expected.
|
// loading as expected and styling everything as expected.
|
||||||
defaultConfig.optimization.splitChunks.cacheGroups.style.name = (
|
defaultConfig.optimization.splitChunks.cacheGroups.style.name = (_, chunks, cacheGroupKey) => {
|
||||||
_,
|
const chunkName = chunks[0].name || ''
|
||||||
chunks,
|
return `${dirname(chunkName)}/${cacheGroupKey}-${basename(chunkName)}`
|
||||||
cacheGroupKey,
|
}
|
||||||
) => {
|
|
||||||
const chunkName = chunks[0].name || "";
|
|
||||||
return `${
|
|
||||||
dirname(
|
|
||||||
chunkName,
|
|
||||||
)
|
|
||||||
}/${cacheGroupKey}-${basename(chunkName)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// This causes the JavaScript chunks to include the chunk hash in the filename,
|
// This causes the JavaScript chunks to include the chunk hash in the filename,
|
||||||
// which is important for cache busting purposes.
|
// which is important for cache busting purposes.
|
||||||
defaultConfig.output.chunkFilename = "[name]-[chunkhash].js";
|
defaultConfig.output.chunkFilename = '[name]-[chunkhash].js'
|
||||||
|
|
||||||
defaultConfig.externals = [
|
module.exports = defaultConfig
|
||||||
/**
|
|
||||||
* The idea here is that we want packages like react, react-dom,
|
|
||||||
* and @wordpress/element to remain as externals to all of our own app's
|
|
||||||
* chunks, and external to all our dependencies that rely on them.
|
|
||||||
*
|
|
||||||
* However, we have a compat.js output that we're building too. And in *that*
|
|
||||||
* bundle, we don't want these things to remain external--that's where we
|
|
||||||
* actually need to bundle them.
|
|
||||||
*
|
|
||||||
* Now, whether we're in WordPress 4, 5, or 6, all of our externals
|
|
||||||
* are going to be based not on the usual globals like window.React, because
|
|
||||||
* of the possibility of colliding with other plugins or themes that may
|
|
||||||
* modify those globals, but on our own globals under __Font_Awesome_Webpack_Externals__.
|
|
||||||
*
|
|
||||||
* Our app's index.js should initialize that __Font_Awesome_Webpack_Externals__
|
|
||||||
* early, before loading any modules that may depend on those externals.
|
|
||||||
*
|
|
||||||
* Under WordPress >=5, the wp_enqueue_script of the main admin JS bundle should
|
|
||||||
* declare the appropriate dependencies, like wp-element. Then our app's
|
|
||||||
* index.js should just set up those __Font_Awesome_Webpack_Externals__
|
|
||||||
* globals as copies of the modules that would be available as part of
|
|
||||||
* WordPress core, like window.wp.element.
|
|
||||||
*
|
|
||||||
* If we're running under WordPress 4, then we should be enqueueing compat.js,
|
|
||||||
* which would load a JavaScript bundle with all of the stuff that would normally
|
|
||||||
* be there for us in WordPress 5. It should set up that __Font_Awesome_Webpack_Externals__
|
|
||||||
* global accordingly.
|
|
||||||
*
|
|
||||||
* So in any case, whether we're running under WordPress 4, 5, or 6, that global
|
|
||||||
* will be set up with the appropriate externals.
|
|
||||||
*
|
|
||||||
* Then in our app's modules--as long as they are loaded after those globals
|
|
||||||
* are set up--we can just do things like:
|
|
||||||
*
|
|
||||||
* import React from 'react'
|
|
||||||
*
|
|
||||||
* or
|
|
||||||
*
|
|
||||||
* import { __ } from '@wordpress/i18n'
|
|
||||||
*
|
|
||||||
* ...and webpack will replace those imports with the right assignments
|
|
||||||
* from that global.
|
|
||||||
*/
|
|
||||||
function ({ context, request }, callback) {
|
|
||||||
switch (request) {
|
|
||||||
case "react":
|
|
||||||
return callback(null, "root __Font_Awesome_Webpack_Externals__.React");
|
|
||||||
case "react-dom":
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
"root __Font_Awesome_Webpack_Externals__.ReactDOM",
|
|
||||||
);
|
|
||||||
case "@wordpress/i18n":
|
|
||||||
return callback(null, "root __Font_Awesome_Webpack_Externals__.i18n");
|
|
||||||
case "@wordpress/api-fetch":
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
"root __Font_Awesome_Webpack_Externals__.apiFetch",
|
|
||||||
);
|
|
||||||
case "@wordpress/components":
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
"root __Font_Awesome_Webpack_Externals__.components",
|
|
||||||
);
|
|
||||||
case "@wordpress/element":
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
"root __Font_Awesome_Webpack_Externals__.element",
|
|
||||||
);
|
|
||||||
case "@wordpress/rich-text":
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
"root __Font_Awesome_Webpack_Externals__.richText",
|
|
||||||
);
|
|
||||||
case "@wordpress/block-editor":
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
"root __Font_Awesome_Webpack_Externals__.blockEditor",
|
|
||||||
);
|
|
||||||
case "@wordpress/dom-ready":
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
"root __Font_Awesome_Webpack_Externals__.domReady",
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
module.exports = defaultConfig;
|
|
||||||
|
|
After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 995 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 232 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 147 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 139 KiB |