diff --git a/.deployignore b/.deployignore deleted file mode 100644 index ca52ad88..00000000 --- a/.deployignore +++ /dev/null @@ -1,52 +0,0 @@ -# Directories and files that we do not want to be included with the built -# IDEs -# version and deployed to WordPress.org. -*.sql -*.tar.gz -*.zip -.DS_Store -.babelrc -.circleci/config.yml -.distignore -.editorconfig -.eslintignore -.eslintrc.json -.git -.gitignore -.gitlab-ci.yml -.idea -.phpcs -.phpcs-cache.json -.phpcs.xml -.phpcs.xml.dist -.phpstan -.phpunit.result.cache -.travis.yml -.vscode -.wordpress-org -.wp-env.json -Gruntfile.js -README.md -Thumbs.db -assets/js/pluginsidebar -assets/js/util -behat.yml -bin -bitbucket-pipelines.yml -multisite.xml -multisite.xml.dist -node_modules -npm-debug.log -phpcs.xml -phpcs.xml.dist -phpstan.neon.dist -phpunit.xml -phpunit.xml.dist -tags -tests -vendor -webpack.config.js -wp-cli.local.yml -yarn.lock -DOCKER_ENV -babel.config.json diff --git a/.distignore b/.distignore deleted file mode 100644 index 852095f1..00000000 --- a/.distignore +++ /dev/null @@ -1,53 +0,0 @@ -# A set of files you probably don't want in your WordPress.org distribution -*.sql -*.tar.gz -*.zip -.DS_Store -.babelrc -.circleci/config.yml -.distignore -.editorconfig -.eslintignore -.eslintrc.json -.git -.github -.gitignore -.gitlab-ci.yml -.idea -.phpcs -.phpcs-cache.json -.phpcs.xml -.phpcs.xml.dist -.phpstan -.phpunit.result.cache -.travis.yml -.vscode -.wordpress-org -.wp-env.json -Gruntfile.js -README.md -Thumbs.db -assets/js/pluginsidebar -assets/js/util -behat.yml -bin -bitbucket-pipelines.yml -composer.json -composer.lock -multisite.xml -multisite.xml.dist -node_modules -npm-debug.log -package-lock.json -package.json -phpcs.xml -phpcs.xml.dist -phpstan.neon.dist -phpunit.xml -phpunit.xml.dist -tags -tests -vendor -webpack.config.js -wp-cli.local.yml -yarn.lock diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 4963d419..00000000 --- a/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -root = true - -# For all files: -# - UTF-8 -# - Unix newlines -# - Insert new line at the end -# - trim whitespace at end of lines -# - Use 2 spaces for indentation -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_style = space -indent_size = 2 - -# For PHP files only: -# - Use tabs (following WordPress conventions) for indentation -# - Show tab width as 2 spaces (inherit from indent_size) -[*.php] -indent_style = tab diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 872f242f..00000000 --- a/.eslintignore +++ /dev/null @@ -1,15 +0,0 @@ -assets/js/bulk-export.js -assets/js/cover-image.js -assets/js/export-table.js -assets/js/json.js -assets/js/meta-boxes.js -assets/js/notices.js -assets/js/preview.js -assets/js/sections.js -assets/js/select2.full.min.js -assets/js/settings.js -assets/js/single-push.js -assets/js/theme-edit.js -assets/js/themes.js -build -vendor diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 2c691872..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true, - "jest": true, - "node": true - }, - "extends": [ - "airbnb", - "airbnb/hooks" - ], - "parser": "@babel/eslint-parser", - "parserOptions": { - "ecmaFeatures": { - "globalReturn": true, - "impliedStrict": true, - "jsx": true - }, - "sourceType": "module" - }, - "rules": { - "no-restricted-syntax": [ - "error", - { - "message": "Ternaries must be used instead of && in JSX expressions to avoid the potential for accidental output. Use, for example, {condition ? : null}.", - "selector": ":matches(JSXElement, JSXFragment) > JSXExpressionContainer > LogicalExpression[operator='&&']" - }, - { - "message": "Ternaries must be used instead of || in JSX expressions to avoid the potential for accidental output. Use, for example, {thing1 ? thing1 : thing2}.", - "selector": ":matches(JSXElement, JSXFragment) > JSXExpressionContainer > LogicalExpression[operator='||']" - } - ] - } -} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6fbcb6cd..00000000 --- a/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -.DS_Store -.phpcs-cache.json -.phpunit.result.cache -*.zip -tags -composer.lock -vendor -node_modules -npm-debug.log -build - -## IDE -.idea -.vscode diff --git a/.phpcs.xml b/.phpcs.xml deleted file mode 100644 index a03a8abc..00000000 --- a/.phpcs.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - PHP_CodeSniffer standard for Publish to Apple News. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - tests/ - - - - - - - - - - - - - - - - - - - build/ - node_modules/ - vendor/ - - - - diff --git a/README.md b/README.md deleted file mode 100644 index 1dfd33a8..00000000 --- a/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Publish to Apple News - -[![read me standard badge](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) - -The Publish to Apple News plugin enables your WordPress content to be published to your Apple News channel. - -- Convert your WordPress content into Apple News format automatically. -- Create a custom design for your Apple News content with no programming knowledge required. -- Automatically or manually publish posts from WordPress to Apple News. -- Control individual posts with options to publish, update, or delete. -- Publish individual posts or in bulk. -- Handles image galleries and popular embeds like YouTube and Vimeo that are supported by Apple News. -- Automatically adjust advertisement settings. - -Please visit our [wiki](https://github.com/alleyinteractive/apple-news/wiki) for detailed information on the follow items: - -- [Background](#background) -- [Releases](#Releases) - - [Install](#install) - - [Use](#use) - - [Source](#from-source) - - [Changelog](#changelog) -- [Development Process](#development-process) - - [Contributing](#contributing) -- [Project Structure](#project-structure) -- [Third-Party Dependencies](#third-party-dependencies) -- [Related Efforts](#related-efforts) -- [Maintainers](#maintainers) -- [License](#license) - -## Background - -To enable content from your WordPress site to be published to your Apple News channel, you must obtain and enter Apple News API credentials from Apple. - -Please see the [Apple Developer](https://developer.apple.com/) and [Apple News Publisher documentation](https://developer.apple.com/news-publisher/) and terms on Apple's website for complete information. - -## Releases - -### Install -See the wiki for [installation instructions](https://github.com/alleyinteractive/apple-news/wiki/Installation). - -### Use -See the wiki for [usage instructions](https://github.com/alleyinteractive/apple-news/wiki/Usage) as well as [configuration guidance](https://github.com/alleyinteractive/apple-news/wiki/Configuration). - -### Source - -### Changelog -See the wiki for the [changelog](https://github.com/alleyinteractive/apple-news/wiki/Changelog). - -## Development Process - -### Contributing -The wiki has [details about contributing](https://github.com/alleyinteractive/apple-news/wiki/Contributing). - -## Project Structure - -## Third-Party Dependencies - -## Related Efforts -- [Connect to Apple Music](https://github.com/alleyinteractive/apple-music) - -## Maintainers -- [Alley](https://github.com/alleyinteractive) - -![Alley logo](https://avatars.githubusercontent.com/u/1733454?s=200&v=4) - -## Releasing the Plugin - -The plugin uses a [built release workflow](./.github/workflows/built-release.yml) -to compile and tag releases. Whenever a new version is detected in the root -`composer.json` file or in the plugin's headers, the workflow will automatically -build the plugin and tag it with a new version. The built tag will contain all -the required front-end assets the plugin may require. This works well for -publishing to WordPress.org or for submodule-ing. - -When you are ready to release a new version of the plugin, you can run -`npm run release` to start the process of setting up a new release. - -### Contributors -Thanks to all of the [contributors](CONTRIBUTORS.md) to this project. - -## License -This project is licensed under the -[GNU Public License (GPL) version 3](LICENSE) or later. diff --git a/assets/js/pluginsidebar/index.jsx b/assets/js/pluginsidebar/index.jsx deleted file mode 100644 index 29c325b9..00000000 --- a/assets/js/pluginsidebar/index.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import { registerPlugin } from '@wordpress/plugins'; -import React from 'react'; - -// Components. -import Icon from '../components/icon'; -import Sidebar from './sidebar'; - -registerPlugin('publish-to-apple-news', { - icon: , - render: Sidebar, -}); diff --git a/assets/js/pluginsidebar/panels/cover-image.jsx b/assets/js/pluginsidebar/panels/cover-image.jsx deleted file mode 100644 index 6d5b1289..00000000 --- a/assets/js/pluginsidebar/panels/cover-image.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import { ImagePicker } from '@alleyinteractive/block-editor-tools'; -import { BaseControl, PanelBody, TextareaControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; -import React from 'react'; - -const CoverImage = ({ - coverImageCaption, - coverImageId, - onChangeCoverImageCaption, - onChangeCoverImageId, -}) => ( - - - onChangeCoverImageId(0)} - onUpdate={({ id }) => onChangeCoverImageId(id)} - value={coverImageId} - /> - - - -); - -CoverImage.propTypes = { - coverImageCaption: PropTypes.string.isRequired, - coverImageId: PropTypes.number.isRequired, - onChangeCoverImageCaption: PropTypes.func.isRequired, - onChangeCoverImageId: PropTypes.func.isRequired, -}; - -export default CoverImage; diff --git a/assets/js/pluginsidebar/panels/maturity-rating.jsx b/assets/js/pluginsidebar/panels/maturity-rating.jsx deleted file mode 100644 index c2e38996..00000000 --- a/assets/js/pluginsidebar/panels/maturity-rating.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import { PanelBody, SelectControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; -import React from 'react'; - -const MaturityRating = ({ - maturityRating, - onChangeMaturityRating, -}) => ( - - - -); - -MaturityRating.propTypes = { - maturityRating: PropTypes.string.isRequired, - onChangeMaturityRating: PropTypes.func.isRequired, -}; - -export default MaturityRating; diff --git a/assets/js/pluginsidebar/panels/metadata.jsx b/assets/js/pluginsidebar/panels/metadata.jsx deleted file mode 100644 index 4555de77..00000000 --- a/assets/js/pluginsidebar/panels/metadata.jsx +++ /dev/null @@ -1,149 +0,0 @@ -import { - Button, - CheckboxControl, - PanelBody, - SelectControl, - TextControl, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; -import React from 'react'; - -// Config. -import { METADATA_SHAPE } from '../../config/prop-types'; - -// Util. -import deleteAtIndex from '../../util/delete-at-index'; -import updateValueAtIndex from '../../util/update-value-at-index'; - -const Metadata = ({ - isHidden, - isPaid, - isPreview, - isSponsored, - metadata, - onChangeIsHidden, - onChangeIsPaid, - onChangeIsPreview, - onChangeIsSponsored, - onChangeMetadata, - onChangeSuppressVideoURL, - onChangeUseImageComponent, - suppressVideoURL, - useImageComponent, -}) => ( - - - - -); - -Metadata.propTypes = { - isHidden: PropTypes.bool.isRequired, - isPaid: PropTypes.bool.isRequired, - isPreview: PropTypes.bool.isRequired, - isSponsored: PropTypes.bool.isRequired, - metadata: PropTypes.arrayOf(PropTypes.shape(METADATA_SHAPE)).isRequired, - onChangeIsHidden: PropTypes.func.isRequired, - onChangeIsPaid: PropTypes.func.isRequired, - onChangeIsPreview: PropTypes.func.isRequired, - onChangeIsSponsored: PropTypes.func.isRequired, - onChangeMetadata: PropTypes.func.isRequired, - onChangeSuppressVideoURL: PropTypes.func.isRequired, - onChangeUseImageComponent: PropTypes.func.isRequired, - suppressVideoURL: PropTypes.bool.isRequired, - useImageComponent: PropTypes.bool.isRequired, -}; - -export default Metadata; diff --git a/assets/js/pluginsidebar/panels/publish-controls.jsx b/assets/js/pluginsidebar/panels/publish-controls.jsx deleted file mode 100644 index 52003762..00000000 --- a/assets/js/pluginsidebar/panels/publish-controls.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import { Button, Spinner } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; -import React from 'react'; - -const PublishControls = ({ - apiAutosync, - apiAutosyncDelete, - apiAutosyncUpdate, - deletePost, - loading, - postIsDirty, - postStatus, - publishPost, - publishState, - updatePost, - userCanPublish, -}) => { - // If the post isn't published, or the user can't publish to Apple News, bail. - if (postStatus !== 'publish' || !userCanPublish) { - return null; - } - - // If we're loading, spin. - if (loading) { - return ; - } - - return ( - <> - {postIsDirty ? ( -
- - {__('Please click the Update button above to ensure that all changes are saved before publishing to Apple News.', 'apple-news')} - -
- ) : null} - {publishState !== 'N/A' && !apiAutosyncUpdate ? ( - - ) : null} - {publishState !== 'N/A' && !apiAutosyncDelete ? ( - - ) : null} - {publishState === 'N/A' && !apiAutosync ? ( - - ) : null} - - ); -}; - -PublishControls.propTypes = { - apiAutosync: PropTypes.bool.isRequired, - apiAutosyncDelete: PropTypes.bool.isRequired, - apiAutosyncUpdate: PropTypes.bool.isRequired, - deletePost: PropTypes.func.isRequired, - loading: PropTypes.bool.isRequired, - postIsDirty: PropTypes.bool.isRequired, - postStatus: PropTypes.string.isRequired, - publishPost: PropTypes.func.isRequired, - publishState: PropTypes.string.isRequired, - updatePost: PropTypes.func.isRequired, - userCanPublish: PropTypes.bool.isRequired, -}; - -export default PublishControls; diff --git a/assets/js/pluginsidebar/panels/publish-info.jsx b/assets/js/pluginsidebar/panels/publish-info.jsx deleted file mode 100644 index 4ceafeb4..00000000 --- a/assets/js/pluginsidebar/panels/publish-info.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { PanelBody } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; -import React from 'react'; - -const PublishInfo = ({ - apiId, - dateCreated, - dateModified, - revision, - shareUrl, - publishState, -}) => { - if (!publishState || publishState === 'N/A') { - return null; - } - - return ( - -

{__('API Id', 'apple-news')}

-

{apiId}

-

{__('Created On', 'apple-news')}

-

{dateCreated}

-

{__('Last Updated On', 'apple-news')}

-

{dateModified}

-

{__('Share URL', 'apple-news')}

-

{shareUrl}

-

{__('Revision', 'apple-news')}

-

{revision}

-

{__('Publish State', 'apple-news')}

-

{publishState}

-
- ); -}; - -PublishInfo.propTypes = { - apiId: PropTypes.string.isRequired, - dateCreated: PropTypes.string.isRequired, - dateModified: PropTypes.string.isRequired, - revision: PropTypes.string.isRequired, - shareUrl: PropTypes.string.isRequired, - publishState: PropTypes.string.isRequired, -}; - -export default PublishInfo; diff --git a/assets/js/pluginsidebar/panels/pull-quote.jsx b/assets/js/pluginsidebar/panels/pull-quote.jsx deleted file mode 100644 index 6e24fd03..00000000 --- a/assets/js/pluginsidebar/panels/pull-quote.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import { - PanelBody, - SelectControl, - TextareaControl, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; -import React from 'react'; - -const PullQuote = ({ - onChangePullquotePosition, - onChangePullquoteText, - pullquotePosition, - pullquoteText, -}) => ( - - - - -); - -PullQuote.propTypes = { - onChangePullquotePosition: PropTypes.func.isRequired, - onChangePullquoteText: PropTypes.func.isRequired, - pullquotePosition: PropTypes.string.isRequired, - pullquoteText: PropTypes.string.isRequired, -}; - -export default PullQuote; diff --git a/assets/js/pluginsidebar/panels/sections.jsx b/assets/js/pluginsidebar/panels/sections.jsx deleted file mode 100644 index 979a25ed..00000000 --- a/assets/js/pluginsidebar/panels/sections.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - BaseControl, - CheckboxControl, - PanelBody, - Spinner, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; -import React from 'react'; - -// Config. -import { SECTION_SHAPE } from '../../config/prop-types'; - -const Sections = ({ - autoAssignCategories, - automaticAssignment, - onChangeAutoAssignCategories, - onChangeSelectedSections, - sections, - selectedSections, -}) => ( - - {!Array.isArray(sections) || sections.length === 0 ? ( - - ) : ( - <> - {automaticAssignment ? ( - - ) : null} - {automaticAssignment && !autoAssignCategories ?
: null} - {(!automaticAssignment || !autoAssignCategories) ? ( - - {sections.map(({ id, name }) => ( - onChangeSelectedSections(id)} - /> - ))} - - ) : null} - - )} -
-); - -Sections.propTypes = { - autoAssignCategories: PropTypes.bool.isRequired, - automaticAssignment: PropTypes.bool.isRequired, - onChangeAutoAssignCategories: PropTypes.func.isRequired, - onChangeSelectedSections: PropTypes.func.isRequired, - sections: PropTypes.arrayOf(PropTypes.shape(SECTION_SHAPE)).isRequired, - selectedSections: PropTypes.arrayOf(PropTypes.string).isRequired, -}; - -export default Sections; diff --git a/assets/js/pluginsidebar/panels/slug.jsx b/assets/js/pluginsidebar/panels/slug.jsx deleted file mode 100644 index 084fa241..00000000 --- a/assets/js/pluginsidebar/panels/slug.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import { - PanelBody, - TextControl, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; -import React from 'react'; - -const Slug = ({ - onChangeSlug, - slug, -}) => ( - - - -); - -Slug.propTypes = { - onChangeSlug: PropTypes.func.isRequired, - slug: PropTypes.string.isRequired, -}; - -export default Slug; diff --git a/assets/js/pluginsidebar/sidebar.jsx b/assets/js/pluginsidebar/sidebar.jsx deleted file mode 100644 index a930649e..00000000 --- a/assets/js/pluginsidebar/sidebar.jsx +++ /dev/null @@ -1,294 +0,0 @@ -import { usePostMeta, usePostMetaValue } from '@alleyinteractive/block-editor-tools'; -import apiFetch from '@wordpress/api-fetch'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { - PluginSidebar, - PluginSidebarMoreMenuItem, -} from '@wordpress/edit-post'; -import { __ } from '@wordpress/i18n'; -import DOMPurify from 'dompurify'; -import React, { useCallback, useEffect, useState } from 'react'; - -// Panels. -import CoverImage from './panels/cover-image'; -import MaturityRating from './panels/maturity-rating'; -import Metadata from './panels/metadata'; -import PublishControls from './panels/publish-controls'; -import PublishInfo from './panels/publish-info'; -import PullQuote from './panels/pull-quote'; -import Sections from './panels/sections'; -import Slug from './panels/slug'; - -// Utils. -import safeJsonParseArray from '../util/safe-json-parse-array'; - -const Sidebar = () => { - const [state, setState] = useState({ - autoAssignCategories: false, - loading: false, - publishState: 'N/A', - sections: [], - settings: { - apiAutosync: false, - apiAutosyncDelete: false, - apiAutosyncUpdate: false, - automaticAssignment: false, - }, - userCanPublish: false, - }); - - // Destructure values out of state for easier access. - const { - autoAssignCategories, - loading, - publishState, - sections, - settings: { - apiAutosync, - apiAutosyncDelete, - apiAutosyncUpdate, - automaticAssignment, - }, - userCanPublish, - } = state; - - // Get a reference to the dispatch function for notices for use later. - const dispatchNotice = useDispatch('core/notices'); - - // Get information about the current post. - const { - notices, - postId, - postIsDirty, - postStatus, - } = useSelect((select) => { - const editor = select('core/editor'); - return { - notices: editor.getEditedPostAttribute('apple_news_notices'), - postId: editor.getCurrentPostId(), - postIsDirty: editor.isEditedPostDirty(), - postStatus: editor.getEditedPostAttribute('status'), - }; - }); - - // Get read-only values from postmeta. - const [{ - apple_news_api_created_at: dateCreated, - apple_news_api_id: apiId, - apple_news_api_modified_at: dateModified, - apple_news_api_revision: revision, - apple_news_api_share_url: shareUrl, - }] = usePostMeta(); - - // Getters and setters for individual postmeta values. - const [coverImageId, setCoverImageId] = usePostMetaValue('apple_news_coverimage'); - const [coverImageCaption, setCoverImageCaption] = usePostMetaValue('apple_news_coverimage_caption'); - const [isHidden, setIsHidden] = usePostMetaValue('apple_news_is_hidden'); - const [isPaid, setIsPaid] = usePostMetaValue('apple_news_is_paid'); - const [isPreview, setIsPreview] = usePostMetaValue('apple_news_is_preview'); - const [isSponsored, setIsSponsored] = usePostMetaValue('apple_news_is_sponsored'); - const [maturityRating, setMaturityRating] = usePostMetaValue('apple_news_maturity_rating'); - const [metadataRaw, setMetadataRaw] = usePostMetaValue('apple_news_metadata'); - const [pullquoteText, setPullquoteText] = usePostMetaValue('apple_news_pullquote'); - const [pullquotePosition, setPullquotePosition] = usePostMetaValue('apple_news_pullquote_position'); - const [selectedSectionsRaw, setSelectedSectionsRaw] = usePostMetaValue('apple_news_sections'); - const [slug, setSlug] = usePostMetaValue('apple_news_slug'); - const [suppressVideoURL, setSuppressVideoURL] = usePostMetaValue('apple_news_suppress_video_url'); - const [useImageComponent, setUseImageComponent] = usePostMetaValue('apple_news_use_image_component'); - - // Decode selected sections. - const metadata = safeJsonParseArray(metadataRaw); - const selectedSections = safeJsonParseArray(selectedSectionsRaw); - - /** - * A helper function for setting metadata. - * @param {object} next - The metadata value to set. - */ - const setMetadata = (next) => setMetadataRaw(JSON.stringify(next)); - - /** - * A helper function for setting selected sections. - * @param {Array} next - The array of selected sections to set. - */ - const setSelectedSections = (next) => setSelectedSectionsRaw(JSON.stringify(next)); - - /** - * A helper function for displaying a notification to the user. - * @param {string} message - The notification message displayed to the user. - * @param {string} type - Optional. The type of message to display. Defaults to success. - */ - const displayNotification = useCallback((message, type = 'success') => (type === 'success' - ? dispatchNotice.createInfoNotice(DOMPurify.sanitize(message), { type: 'snackbar' }) - : dispatchNotice.createErrorNotice(message, { __unstableHTML: true }) - ), [dispatchNotice]); - - /** - * Sends a request to the REST API to modify the post. - * @param {string} operation - One of delete, publish, update. - */ - const modifyPost = async (operation) => { - setState({ - ...state, - loading: true, - }); - - try { - const { - notifications = [], - publishState: nextPublishState = '', - } = await apiFetch({ - data: { - id: postId, - }, - method: 'POST', - path: `/apple-news/v1/${operation}`, - }); - notifications.forEach((notification) => displayNotification( - notification.message, - notification.type, - )); - setState({ - ...state, - loading: false, - publishState: nextPublishState, - }); - } catch (error) { - displayNotification(error.message, 'error'); - setState({ - ...state, - loading: false, - }); - } - }; - - /** - * A helper function to update which sections are selected. - * @param {string} id - The id of the section to toggle. - */ - const toggleSelectedSection = (id) => setSelectedSections( - selectedSections.includes(id) - ? selectedSections.filter((section) => section !== id) - : [...selectedSections, id], - ); - - // On initial load, fetch info from the API into state. - useEffect(() => { - (async () => { - const fetches = [ - await apiFetch({ path: `/apple-news/v1/get-published-state/${postId}` }), - await apiFetch({ path: '/apple-news/v1/sections' }), - await apiFetch({ path: '/apple-news/v1/get-settings' }), - await apiFetch({ path: `/apple-news/v1/user-can-publish/${postId}` }), - ]; - - // Wait for everything to load, update state, and handle errors. - try { - const data = await Promise.all(fetches); - setState({ - ...state, - autoAssignCategories: (selectedSections === null || selectedSections.length === 0) - && data[2].automaticAssignment === true, - ...data[0], - sections: data[1], - settings: data[2], - ...data[3], - }); - } catch (error) { - displayNotification(error.message, 'error'); - } - })(); - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - // Display notices whenever they change. - useEffect(() => { - notices.forEach((notice) => displayNotification(notice.message, notice.type)); - }, [displayNotification, notices]); - - return ( - <> - - {__('Apple News Options', 'apple-news')} - - - { - setState({ - ...state, - autoAssignCategories: next, - }); - setSelectedSections([]); - }} - onChangeSelectedSections={toggleSelectedSection} - sections={sections} - selectedSections={selectedSections} - /> - - - - - - {publishState !== 'N/A' ? ( - - ) : null} - modifyPost('delete')} - loading={loading} - postIsDirty={postIsDirty} - postStatus={postStatus} - publishPost={() => modifyPost('publish')} - publishState={publishState} - updatePost={() => modifyPost('update')} - userCanPublish={userCanPublish} - /> - - - ); -}; - -export default Sidebar; diff --git a/assets/js/services/hooks/use-site-options/README.md b/assets/js/services/hooks/use-site-options/README.md deleted file mode 100644 index 7b97d6dd..00000000 --- a/assets/js/services/hooks/use-site-options/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Custom Hooks: useSiteOptions -Get and set site options via `apiFetch`. Inherits user's capabilities and returns an error notice to the snackbar if the user is not able to fetch or set options. - -Utilize also `notices` to return the snackbar messages. -## Usage -### Getting site settings - -```jsx -const [{ loading, saving, settings }, setOptions] = useSiteOptions(); -``` - -Utilize the settings object as the object containing settings available to the user. - -### Setting site settings. -Expects the full settings object on save. Spread settings, and set the new key/value pair as the second param. - -```jsx -(next) => setHolder({ ...settings, options_key: next }) -``` diff --git a/assets/js/services/hooks/use-taxonomies/README.md b/assets/js/services/hooks/use-taxonomies/README.md deleted file mode 100644 index 02afb605..00000000 --- a/assets/js/services/hooks/use-taxonomies/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Custom Hooks: useTaxonomies - -Get and cache taxonomies config via `apiFetch`. - -## Usage - -```jsx -const taxonomies = useTaxonomies(); -``` - -Returns the API response from /wp/v2/taxonomies. Caches it for future use. diff --git a/assets/js/services/hooks/use-term-cache/README.md b/assets/js/services/hooks/use-term-cache/README.md deleted file mode 100644 index f0590ecf..00000000 --- a/assets/js/services/hooks/use-term-cache/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Custom Hooks: useTermCache - -Get and set terms from a cache for from various taxonomies. - -## Usage - -```jsx -const termCache = useTermCache(); -const myTerm = termCache.get('category', 5); -termCache.set({ /* REST response here */ }); -``` - -Returns the API response for the term ID in the given taxonomy. Caches it for -future use. diff --git a/assets/js/util/delete-at-index.js b/assets/js/util/delete-at-index.js deleted file mode 100644 index 06d94bc4..00000000 --- a/assets/js/util/delete-at-index.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Given an array of values, returns a copy of the array with the value at the - * given index removed. - * @param {Array} values - The array of values to modify. - * @param {number} index - The index to remove. - * @returns {Array} A copy of the values array with the value at the specified index removed. - */ -const deleteAtIndex = (values, index) => values.filter((value, idx) => index !== idx); - -export default deleteAtIndex; diff --git a/assets/js/util/delete-at-index.test.js b/assets/js/util/delete-at-index.test.js deleted file mode 100644 index 1529ffdf..00000000 --- a/assets/js/util/delete-at-index.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import deleteAtIndex from './delete-at-index'; - -test('deleteAtIndex should properly delete items at a given index.', () => { - const values = ['a', 'b', 'c']; - expect(deleteAtIndex(values, 0)).toEqual(['b', 'c']); - expect(deleteAtIndex(values, 1)).toEqual(['a', 'c']); - expect(deleteAtIndex(values, 2)).toEqual(['a', 'b']); -}); diff --git a/assets/js/util/safe-json-parse-array.js b/assets/js/util/safe-json-parse-array.js deleted file mode 100644 index 515ff502..00000000 --- a/assets/js/util/safe-json-parse-array.js +++ /dev/null @@ -1,21 +0,0 @@ -import safeJsonParse from './safe-json-parse'; - -/** - * Given a value, run JSON.parse on it, but if parsing fails, or if - * what results from the parse is not an array, return an empty - * array rather than a syntax error or a value of another type. - * @param {*} value - The value to attempt to parse. - * @returns {array} - The parsed value, or an empty array on failure. - */ -const safeJsonParseArray = (value) => { - const parsedValue = safeJsonParse(value); - - // Make absolutely sure that the parsed value is an array. - if (!Array.isArray(parsedValue)) { - return []; - } - - return parsedValue; -}; - -export default safeJsonParseArray; diff --git a/assets/js/util/safe-json-parse-array.test.js b/assets/js/util/safe-json-parse-array.test.js deleted file mode 100644 index 1f91cea8..00000000 --- a/assets/js/util/safe-json-parse-array.test.js +++ /dev/null @@ -1,16 +0,0 @@ -import safeJsonParseArray from './safe-json-parse-array'; - -test('safeJsonParseArray should properly return a parsed array.', () => { - expect(safeJsonParseArray('[1, 5, "false"]')).toEqual([1, 5, 'false']); - expect(safeJsonParseArray('["a", "b", "c"]')).toEqual(['a', 'b', 'c']); -}); - -test('safeJsonParseArray should return an empty array for any non-array types.', () => { - expect(safeJsonParseArray('true')).toEqual([]); - expect(safeJsonParseArray('"foo"')).toEqual([]); - expect(safeJsonParseArray('null')).toEqual([]); - expect(safeJsonParseArray('{}')).toEqual([]); - expect(safeJsonParseArray('{"a": "b"}')).toEqual([]); - expect(safeJsonParseArray('')).toEqual([]); - expect(safeJsonParseArray(undefined)).toEqual([]); -}); diff --git a/assets/js/util/safe-json-parse-object.js b/assets/js/util/safe-json-parse-object.js deleted file mode 100644 index 952c7883..00000000 --- a/assets/js/util/safe-json-parse-object.js +++ /dev/null @@ -1,25 +0,0 @@ -import safeJsonParse from './safe-json-parse'; - -/** - * Given a value, run JSON.parse on it, but if parsing fails, or if - * what results from the parse is not a standard object, return an empty - * object rather than a syntax error or a value of another type. - * @param {*} value - The value to attempt to parse. - * @returns {object} - The parsed value, or an empty object on failure. - */ -const safeJsonParseObject = (value) => { - const parsedValue = safeJsonParse(value); - - // Make absolutely sure that the object is a standard object. - if (parsedValue === null - || typeof parsedValue !== 'object' - || Array.isArray(parsedValue) - || JSON.stringify(parsedValue).indexOf('{') !== 0 - ) { - return {}; - } - - return parsedValue; -}; - -export default safeJsonParseObject; diff --git a/assets/js/util/safe-json-parse-object.test.js b/assets/js/util/safe-json-parse-object.test.js deleted file mode 100644 index fe607b1f..00000000 --- a/assets/js/util/safe-json-parse-object.test.js +++ /dev/null @@ -1,16 +0,0 @@ -import safeJsonParseObject from './safe-json-parse-object'; - -test('safeJsonParseObject should properly return a parsed object.', () => { - expect(safeJsonParseObject('{}')).toEqual({}); - expect(safeJsonParseObject('{"a": "b"}')).toEqual({ a: 'b' }); -}); - -test('safeJsonParseObject should return an empty object for any non-object types.', () => { - expect(safeJsonParseObject('true')).toEqual({}); - expect(safeJsonParseObject('"foo"')).toEqual({}); - expect(safeJsonParseObject('[1, 5, "false"]')).toEqual({}); - expect(safeJsonParseObject('null')).toEqual({}); - expect(safeJsonParseObject('["a", "b", "c"]')).toEqual({}); - expect(safeJsonParseObject('')).toEqual({}); - expect(safeJsonParseObject(undefined)).toEqual({}); -}); diff --git a/assets/js/util/safe-json-parse.js b/assets/js/util/safe-json-parse.js deleted file mode 100644 index 786a8799..00000000 --- a/assets/js/util/safe-json-parse.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Given a value, run JSON.parse on it, but if parsing fails, return null - * instead of throwing a SyntaxError. - * @param {*} value - The value to attempt to parse. - * @returns {*} - The parsed value, or null on failure. - */ -const safeJsonParse = (value) => { - try { - return JSON.parse(value); - } catch (e) { - return null; - } -}; - -export default safeJsonParse; diff --git a/assets/js/util/safe-json-parse.test.js b/assets/js/util/safe-json-parse.test.js deleted file mode 100644 index 82884632..00000000 --- a/assets/js/util/safe-json-parse.test.js +++ /dev/null @@ -1,16 +0,0 @@ -import safeJsonParse from './safe-json-parse'; - -test('safeJsonParse should properly decode valid JSON.', () => { - expect(safeJsonParse('{}')).toEqual({}); - expect(safeJsonParse('true')).toEqual(true); - expect(safeJsonParse('"foo"')).toEqual('foo'); - expect(safeJsonParse('[1, 5, "false"]')).toEqual([1, 5, 'false']); - expect(safeJsonParse('null')).toEqual(null); - expect(safeJsonParse('["a", "b", "c"]')).toEqual(['a', 'b', 'c']); - expect(safeJsonParse('{"a": "b"}')).toEqual({ a: 'b' }); -}); - -test('Should not choke on invalid JSON.', () => { - expect(safeJsonParse('')).toEqual(null); - expect(safeJsonParse(undefined)).toEqual(null); -}); diff --git a/assets/js/util/update-value-at-index.js b/assets/js/util/update-value-at-index.js deleted file mode 100644 index 7ac7dfd2..00000000 --- a/assets/js/util/update-value-at-index.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Given an array of objects, a key, and a value, returns a copy of the array - * with the value for the key set at the given index. - * @param {Array} values - An array of objects. - * @param {string} key - The object key to update. - * @param {*} value - The value to set for the key. - * @param {number} index - The index to set the value on. - * @returns {Array} A copy of the array with the value set for the key at the given index. - */ -const updateValueAtIndex = (values, key, value, index) => { - const valuesCopy = values.map((item) => ({ ...item })); - valuesCopy[index][key] = value; - return valuesCopy; -}; - -export default updateValueAtIndex; diff --git a/assets/js/util/update-value-at-index.test.js b/assets/js/util/update-value-at-index.test.js deleted file mode 100644 index 388d6277..00000000 --- a/assets/js/util/update-value-at-index.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import updateValueAtIndex from './update-value-at-index'; - -test('updateValueAtIndex should properly update values at indices.', () => { - const values = [ - { a: 'b', c: 'd' }, - { e: 'f', g: 'h' }, - { i: 'j', k: 'l' }, - ]; - expect(updateValueAtIndex(values, 'a', 'x', 0)).toEqual([ - { a: 'x', c: 'd' }, - { e: 'f', g: 'h' }, - { i: 'j', k: 'l' }, - ]); - expect(updateValueAtIndex(values, 'c', 'x', 0)).toEqual([ - { a: 'b', c: 'x' }, - { e: 'f', g: 'h' }, - { i: 'j', k: 'l' }, - ]); - expect(updateValueAtIndex(values, 'e', 'x', 1)).toEqual([ - { a: 'b', c: 'd' }, - { e: 'x', g: 'h' }, - { i: 'j', k: 'l' }, - ]); - expect(updateValueAtIndex(values, 'g', 'x', 1)).toEqual([ - { a: 'b', c: 'd' }, - { e: 'f', g: 'x' }, - { i: 'j', k: 'l' }, - ]); - expect(updateValueAtIndex(values, 'i', 'x', 2)).toEqual([ - { a: 'b', c: 'd' }, - { e: 'f', g: 'h' }, - { i: 'x', k: 'l' }, - ]); - expect(updateValueAtIndex(values, 'k', 'x', 2)).toEqual([ - { a: 'b', c: 'd' }, - { e: 'f', g: 'h' }, - { i: 'j', k: 'x' }, - ]); -}); diff --git a/babel.config.json b/babel.config.json deleted file mode 100644 index 7e07e6cc..00000000 --- a/babel.config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": [ - "babel-plugin-styled-components" - ], - "presets": [ - "@babel/preset-env", - "@babel/preset-react" - ] -} diff --git a/build/adminSettings.asset.php b/build/adminSettings.asset.php new file mode 100644 index 00000000..37891732 --- /dev/null +++ b/build/adminSettings.asset.php @@ -0,0 +1 @@ + array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-i18n'), 'version' => '224b3bcbfabe83e2400b1c89e956ac1f'); \ No newline at end of file diff --git a/build/adminSettings.js b/build/adminSettings.js new file mode 100644 index 00000000..e035b4b0 --- /dev/null +++ b/build/adminSettings.js @@ -0,0 +1,2 @@ +!function(){var e={373:function(e){var t;self,t=()=>(()=>{var e={184:(e,t)=>{var r;!function(){"use strict";var n={}.hasOwnProperty;function o(){for(var e=[],t=0;t{"use strict";r.d(t,{Z:()=>s});var n=r(537),o=r.n(n),i=r(645),a=r.n(i)()(o());a.push([e.id,'.edit-post-sidebar .autocomplete__component,.editor-styles-wrapper .autocomplete__component{margin-bottom:20px}.edit-post-sidebar .autocomplete-base-control,.editor-styles-wrapper .autocomplete-base-control{position:relative}.edit-post-sidebar .autocomplete-text-control__input,.editor-styles-wrapper .autocomplete-text-control__input{margin:0}.edit-post-sidebar .autocomplete__selection-list,.editor-styles-wrapper .autocomplete__selection-list{list-style-type:none;margin:0 0 6px;padding:0}.edit-post-sidebar .autocomplete__selection-list--item,.editor-styles-wrapper .autocomplete__selection-list--item{display:inline-block;list-style:none}.edit-post-sidebar .autocomplete__selection-list--item--button,.editor-styles-wrapper .autocomplete__selection-list--item--button{margin-bottom:4px;margin-right:3px}.edit-post-sidebar .autocomplete__selection-list--item--button::after,.editor-styles-wrapper .autocomplete__selection-list--item--button::after{content:"×";font-size:16px;line-height:20px;margin-left:5px}.edit-post-sidebar .autocomplete__dropdown,.editor-styles-wrapper .autocomplete__dropdown{background-color:#fff;border-color:rgba(0,0,0,0) #e2e4e7 #e2e4e7;border-radius:0 0 4px 4px;border-style:solid;border-width:0 1px 1px;left:0;max-height:0;overflow-y:hidden;position:absolute;top:calc(100% + 1px);visibility:hidden;width:100%;z-index:10}.edit-post-sidebar .autocomplete__dropdown--is-open,.editor-styles-wrapper .autocomplete__dropdown--is-open{box-shadow:0 3px 30px rgba(25,30,35,.1);max-height:225px;overflow-y:scroll;visibility:visible}.edit-post-sidebar .autocomplete__dropdown--notice,.editor-styles-wrapper .autocomplete__dropdown--notice{padding:15px}.edit-post-sidebar .autocomplete__dropdown--results,.editor-styles-wrapper .autocomplete__dropdown--results{list-style:none;margin:0;padding:0}.edit-post-sidebar .autocomplete__list--item,.editor-styles-wrapper .autocomplete__list--item{list-style:none}.edit-post-sidebar .autocomplete__list--item>button,.editor-styles-wrapper .autocomplete__list--item>button{background:rgba(0,0,0,0);border-color:#e2e4e7;border-style:solid;border-width:0 0 1px;height:100%;line-height:1.25;text-align:left;white-space:inherit;width:100%}.edit-post-sidebar .autocomplete__list--item:last-child>button,.editor-styles-wrapper .autocomplete__list--item:last-child>button{border-bottom:0}',"",{version:3,sources:["webpack://./src/components/selector/styles.scss"],names:[],mappings:"AAAA,4FAgBI,kBACE,CAAA,gGAMF,iBACE,CAAA,8GAMF,QACE,CAAA,sGAMF,oBACE,CAAA,cACA,CAAA,SACA,CAAA,kHAEA,oBACE,CAAA,eACA,CAAA,kIAEA,iBACE,CAAA,gBACA,CAAA,gJAEA,WACE,CAAA,cACA,CAAA,gBACA,CAAA,eACA,CAAA,0FASR,qBACE,CAAA,0CACA,CAAA,yBACA,CAAA,kBACA,CAAA,sBACA,CAAA,MACA,CAAA,YACA,CAAA,iBACA,CAAA,iBACA,CAAA,oBACA,CAAA,iBACA,CAAA,UACA,CAAA,UACA,CAAA,4GAGA,uCACE,CAAA,gBACA,CAAA,iBACA,CAAA,kBACA,CAAA,0GAIF,YACE,CAAA,4GAIF,eACE,CAAA,QACA,CAAA,SACA,CAAA,8FAOJ,eACE,CAAA,4GAEA,wBACE,CAAA,oBACA,CAAA,kBACA,CAAA,oBACA,CAAA,WACA,CAAA,gBACA,CAAA,eACA,CAAA,mBACA,CAAA,UACA,CAAA,kIAGF,eACE",sourcesContent:["//--------------------------------------------------------------\n// AutoComplete Styles\n//--------------------------------------------------------------\n\n/* stylelint-disable max-nesting-depth */\n\n//-----------------------------------------\n// Accommodate editor well, or the sidebar.\n//-----------------------------------------\n.edit-post-sidebar,\n.editor-styles-wrapper {\n .autocomplete {\n\n //-----------------------------------------\n // Parent form wrapper.\n //-----------------------------------------\n &__component {\n margin-bottom: 20px;\n }\n\n //-----------------------------------------\n // Wrapper\n //-----------------------------------------\n &-base-control {\n position: relative;\n }\n\n //-----------------------------------------\n // Input\n //-----------------------------------------\n &-text-control__input {\n margin: 0;\n }\n\n //-----------------------------------------\n // Selected buttons.\n //-----------------------------------------\n &__selection-list {\n list-style-type: none;\n margin: 0 0 6px;\n padding: 0;\n\n &--item {\n display: inline-block;\n list-style: none;\n\n &--button {\n margin-bottom: 4px;\n margin-right: 3px;\n\n &::after {\n content: '×';\n font-size: 16px;\n line-height: 20px;\n margin-left: 5px;\n }\n }\n }\n }\n\n //-----------------------------------------\n // Results\n //-----------------------------------------\n &__dropdown {\n background-color: #fff;\n border-color: transparent #e2e4e7 #e2e4e7;\n border-radius: 0 0 4px 4px;\n border-style: solid;\n border-width: 0 1px 1px;\n left: 0;\n max-height: 0;\n overflow-y: hidden;\n position: absolute;\n top: calc(100% + 1px); // Offset focus border.\n visibility: hidden;\n width: 100%;\n z-index: 10;\n\n // Container is open.\n &--is-open {\n box-shadow: 0 3px 30px rgba(25, 30, 35, 0.1);\n max-height: 225px;\n overflow-y: scroll;\n visibility: visible;\n }\n\n // Notice handler.\n &--notice {\n padding: 15px;\n }\n\n // Results container.\n &--results {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n }\n\n //-----------------------------------------\n // List/Results\n //-----------------------------------------\n &__list--item {\n list-style: none;\n\n > button {\n background: transparent;\n border-color: #e2e4e7;\n border-style: solid;\n border-width: 0 0 1px;\n height: 100%;\n line-height: 1.25;\n text-align: left;\n white-space: inherit;\n width: 100%;\n }\n\n &:last-child > button {\n border-bottom: 0;\n }\n }\n }\n}\n"],sourceRoot:""}]);const s=a},645:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var r="",n=void 0!==t[5];return t[4]&&(r+="@supports (".concat(t[4],") {")),t[2]&&(r+="@media ".concat(t[2]," {")),n&&(r+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),r+=e(t),n&&(r+="}"),t[2]&&(r+="}"),t[4]&&(r+="}"),r})).join("")},t.i=function(e,r,n,o,i){"string"==typeof e&&(e=[[null,e,void 0]]);var a={};if(n)for(var s=0;s0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=i),r&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=r):u[2]=r),o&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=o):u[4]="".concat(o)),t.push(u))}},t}},537:e=>{"use strict";e.exports=function(e){var t=e[1],r=e[3];if(!r)return t;if("function"==typeof btoa){var n=btoa(unescape(encodeURIComponent(JSON.stringify(r)))),o="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(n),i="/*# ".concat(o," */"),a=r.sources.map((function(e){return"/*# sourceURL=".concat(r.sourceRoot||"").concat(e," */")}));return[t].concat(a).concat([i]).join("\n")}return[t].join("\n")}},856:function(e){e.exports=function(){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,r){return t=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},t(e,r)}function r(e,n,o){return r=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}()?Reflect.construct:function(e,r,n){var o=[null];o.push.apply(o,r);var i=new(Function.bind.apply(e,o));return n&&t(i,n.prototype),i},r.apply(null,arguments)}function n(e){return function(e){if(Array.isArray(e))return o(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(e){if("string"==typeof e)return o(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?o(e,t):void 0}}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r1?r-1:0),o=1;o/gm),H=p(/^data-[\-\w.\u00B7-\uFFFF]/),G=p(/^aria-[\-\w]+$/),W=p(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),V=p(/^(?:\w+script|data):/i),Y=p(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),K=p(/^html$/i);return function t(){var r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"undefined"==typeof window?null:window,o=function(e){return t(e)};if(o.version="2.3.9",o.removed=[],!r||!r.document||9!==r.document.nodeType)return o.isSupported=!1,o;var i=r.document,a=r.document,s=r.DocumentFragment,l=r.HTMLTemplateElement,c=r.Node,p=r.Element,d=r.NodeFilter,f=r.NamedNodeMap,m=void 0===f?r.NamedNodeMap||r.MozNamedAttrMap:f,h=r.HTMLFormElement,y=r.DOMParser,x=r.trustedTypes,X=p.prototype,Z=O(X,"cloneNode"),J=O(X,"nextSibling"),Q=O(X,"childNodes"),ee=O(X,"parentNode");if("function"==typeof l){var te=a.createElement("template");te.content&&te.content.ownerDocument&&(a=te.content.ownerDocument)}var re=function(t,r){if("object"!==e(t)||"function"!=typeof t.createPolicy)return null;var n=null,o="data-tt-policy-suffix";r.currentScript&&r.currentScript.hasAttribute(o)&&(n=r.currentScript.getAttribute(o));var i="dompurify"+(n?"#"+n:"");try{return t.createPolicy(i,{createHTML:function(e){return e}})}catch(e){return console.warn("TrustedTypes policy "+i+" could not be created."),null}}(x,i),ne=re?re.createHTML(""):"",oe=a,ie=oe.implementation,ae=oe.createNodeIterator,se=oe.createDocumentFragment,le=oe.getElementsByTagName,ce=i.importNode,ue={};try{ue=T(a).documentMode?a.documentMode:{}}catch(e){}var pe={};o.isSupported="function"==typeof ee&&ie&&void 0!==ie.createHTMLDocument&&9!==ue;var de,fe,me=q,he=$,ye=H,ge=G,ve=V,be=Y,we=W,_e=null,Ae=R({},[].concat(n(I),n(N),n(L),n(j),n(M))),Se=null,Ce=R({},[].concat(n(U),n(F),n(z),n(B))),Ee=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),ke=null,xe=null,Re=!0,Te=!0,Oe=!1,Ie=!1,Ne=!1,Le=!1,Pe=!1,je=!1,De=!1,Me=!1,Ue=!0,Fe=!0,ze=!1,Be={},qe=null,$e=R({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),He=null,Ge=R({},["audio","video","img","source","image","track"]),We=null,Ve=R({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ye="http://www.w3.org/1998/Math/MathML",Ke="http://www.w3.org/2000/svg",Xe="http://www.w3.org/1999/xhtml",Ze=Xe,Je=!1,Qe=["application/xhtml+xml","text/html"],et=null,tt=a.createElement("form"),rt=function(e){return e instanceof RegExp||e instanceof Function},nt=function(t){et&&et===t||(t&&"object"===e(t)||(t={}),t=T(t),de=de=-1===Qe.indexOf(t.PARSER_MEDIA_TYPE)?"text/html":t.PARSER_MEDIA_TYPE,fe="application/xhtml+xml"===de?function(e){return e}:w,_e="ALLOWED_TAGS"in t?R({},t.ALLOWED_TAGS,fe):Ae,Se="ALLOWED_ATTR"in t?R({},t.ALLOWED_ATTR,fe):Ce,We="ADD_URI_SAFE_ATTR"in t?R(T(Ve),t.ADD_URI_SAFE_ATTR,fe):Ve,He="ADD_DATA_URI_TAGS"in t?R(T(Ge),t.ADD_DATA_URI_TAGS,fe):Ge,qe="FORBID_CONTENTS"in t?R({},t.FORBID_CONTENTS,fe):$e,ke="FORBID_TAGS"in t?R({},t.FORBID_TAGS,fe):{},xe="FORBID_ATTR"in t?R({},t.FORBID_ATTR,fe):{},Be="USE_PROFILES"in t&&t.USE_PROFILES,Re=!1!==t.ALLOW_ARIA_ATTR,Te=!1!==t.ALLOW_DATA_ATTR,Oe=t.ALLOW_UNKNOWN_PROTOCOLS||!1,Ie=t.SAFE_FOR_TEMPLATES||!1,Ne=t.WHOLE_DOCUMENT||!1,je=t.RETURN_DOM||!1,De=t.RETURN_DOM_FRAGMENT||!1,Me=t.RETURN_TRUSTED_TYPE||!1,Pe=t.FORCE_BODY||!1,Ue=!1!==t.SANITIZE_DOM,Fe=!1!==t.KEEP_CONTENT,ze=t.IN_PLACE||!1,we=t.ALLOWED_URI_REGEXP||we,Ze=t.NAMESPACE||Xe,t.CUSTOM_ELEMENT_HANDLING&&rt(t.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Ee.tagNameCheck=t.CUSTOM_ELEMENT_HANDLING.tagNameCheck),t.CUSTOM_ELEMENT_HANDLING&&rt(t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Ee.attributeNameCheck=t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),t.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Ee.allowCustomizedBuiltInElements=t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Ie&&(Te=!1),De&&(je=!0),Be&&(_e=R({},n(M)),Se=[],!0===Be.html&&(R(_e,I),R(Se,U)),!0===Be.svg&&(R(_e,N),R(Se,F),R(Se,B)),!0===Be.svgFilters&&(R(_e,L),R(Se,F),R(Se,B)),!0===Be.mathMl&&(R(_e,j),R(Se,z),R(Se,B))),t.ADD_TAGS&&(_e===Ae&&(_e=T(_e)),R(_e,t.ADD_TAGS,fe)),t.ADD_ATTR&&(Se===Ce&&(Se=T(Se)),R(Se,t.ADD_ATTR,fe)),t.ADD_URI_SAFE_ATTR&&R(We,t.ADD_URI_SAFE_ATTR,fe),t.FORBID_CONTENTS&&(qe===$e&&(qe=T(qe)),R(qe,t.FORBID_CONTENTS,fe)),Fe&&(_e["#text"]=!0),Ne&&R(_e,["html","head","body"]),_e.table&&(R(_e,["tbody"]),delete ke.tbody),u&&u(t),et=t)},ot=R({},["mi","mo","mn","ms","mtext"]),it=R({},["foreignobject","desc","title","annotation-xml"]),at=R({},["title","style","font","a","script"]),st=R({},N);R(st,L),R(st,P);var lt=R({},j);R(lt,D);var ct=function(e){b(o.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=ne}catch(t){e.remove()}}},ut=function(e,t){try{b(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){b(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Se[e])if(je||De)try{ct(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},pt=function(e){var t,r;if(Pe)e=""+e;else{var n=_(e,/^[\r\n\t ]+/);r=n&&n[0]}"application/xhtml+xml"===de&&(e=''+e+"");var o=re?re.createHTML(e):e;if(Ze===Xe)try{t=(new y).parseFromString(o,de)}catch(e){}if(!t||!t.documentElement){t=ie.createDocument(Ze,"template",null);try{t.documentElement.innerHTML=Je?"":o}catch(e){}}var i=t.body||t.documentElement;return e&&r&&i.insertBefore(a.createTextNode(r),i.childNodes[0]||null),Ze===Xe?le.call(t,Ne?"html":"body")[0]:Ne?t.documentElement:i},dt=function(e){return ae.call(e.ownerDocument||e,e,d.SHOW_ELEMENT|d.SHOW_COMMENT|d.SHOW_TEXT,null,!1)},ft=function(t){return"object"===e(c)?t instanceof c:t&&"object"===e(t)&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName},mt=function(e,t,r){pe[e]&&g(pe[e],(function(e){e.call(o,t,r,et)}))},ht=function(e){var t;if(mt("beforeSanitizeElements",e,null),function(e){return e instanceof h&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof m)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore)}(e))return ct(e),!0;if(E(/[\u0080-\uFFFF]/,e.nodeName))return ct(e),!0;var r=fe(e.nodeName);if(mt("uponSanitizeElement",e,{tagName:r,allowedTags:_e}),e.hasChildNodes()&&!ft(e.firstElementChild)&&(!ft(e.content)||!ft(e.content.firstElementChild))&&E(/<[/\w]/g,e.innerHTML)&&E(/<[/\w]/g,e.textContent))return ct(e),!0;if("select"===r&&E(/