diff --git a/packages/dev/docs/package.json b/packages/dev/docs/package.json index 74c9519f1ff..654f8f044c9 100644 --- a/packages/dev/docs/package.json +++ b/packages/dev/docs/package.json @@ -23,7 +23,9 @@ "@spectrum-icons/illustrations": "^3.1.0", "@spectrum-icons/ui": "^3.1.0", "@spectrum-icons/workflow": "^4.0.0", + "algoliasearch": "^4.14.1", "clsx": "^1.1.1", + "dompurify": "^2.3.10", "globals-docs": "^2.4.1", "highlight.js": "9.18.1", "markdown-to-jsx": "^6.11.0", diff --git a/packages/dev/docs/src/DocSearch.js b/packages/dev/docs/src/DocSearch.js new file mode 100644 index 00000000000..b93341b0fd5 --- /dev/null +++ b/packages/dev/docs/src/DocSearch.js @@ -0,0 +1,347 @@ +import algoliasearch from 'algoliasearch/lite'; +import docsStyle from './docs.css'; +import DocumentOutline from '@spectrum-icons/workflow/DocumentOutline'; +import DOMPurify from 'dompurify'; +import {Icon} from '@react-spectrum/icon'; +import {Item, SearchAutocomplete, Section} from '@react-spectrum/autocomplete'; +import News from '@spectrum-icons/workflow/News'; +import React, {useEffect, useRef, useState} from 'react'; +import {Text} from '@adobe/react-spectrum'; +import {ThemeProvider} from './ThemeSwitcher'; + +let client = algoliasearch('1V1Q59JVTR', '44a7e2e7508ff185f25ac64c0a675f98'); +let searchIndex = client.initIndex('react-spectrum'); + +const searchOptions = { + distinct: 1, + attributesToRetrieve: [ + 'hierarchy.lvl0', + 'hierarchy.lvl1', + 'hierarchy.lvl2', + 'hierarchy.lvl3', + 'hierarchy.lvl4', + 'hierarchy.lvl5', + 'hierarchy.lvl6', + 'content', + 'type', + 'url' + ], + attributesToSnippet: [ + 'hierarchy.lvl1:10', + 'hierarchy.lvl2:10', + 'hierarchy.lvl3:10', + 'hierarchy.lvl4:10', + 'hierarchy.lvl5:10', + 'hierarchy.lvl6:10', + 'content:10' + ], + snippetEllipsisText: '…', + highlightPreTag: ``, + highlightPostTag: '', + hitsPerPage: 20 +}; + +const sectionTitles = { + 'react-aria': 'React Aria', + 'react-spectrum': 'React Spectrum', + 'react-stately': 'React Stately', + 'internationalized': 'Internationalized', + 'blog': 'Blog', + 'architecture': 'Architecture', + 'contribute': 'Contribute', + 'releases': 'Releases', + 'support': 'Support' +}; + +function sectionTitlePredicate(hit) { + let sectionTitle; + for (const [path, title] of Object.entries(sectionTitles)) { + let regexp = new RegExp('^.+//.+/' + path + '[/.].+$', 'i'); + if (hit.url.match(regexp)) { + sectionTitle = title; + break; + } + } + if (!sectionTitle) { + sectionTitle = 'Documentation'; + } + return sectionTitle; +} + +export default function DocSearch() { + const [searchValue, setSearchValue] = useState(''); + const [loadingState, setLoadingState] = useState(null); + const [predictions, setPredictions] = useState(null); + const [suggestions, setSuggestions] = useState([]); + + const searchAutocompleteRef = useRef(); + + useEffect(() => { + // Focus search input when user presses Ctrl/Cmd + K + let handleKeyDown = (e) => { + if ((e.ctrlKey || e.metaKey) && e.key === 'k') { + e.preventDefault(); + searchAutocompleteRef.current.focus(); + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, []); + + let updatePredictions = ({hits}) => { + setPredictions(hits); + + const groupedBySection = groupBy(hits, (hit) => sectionTitlePredicate(hit)); + let sections = []; + for (const [title, hits] of Object.entries(groupedBySection)) { + const items = Object.values( + groupBy(hits, (hit) => hit.hierarchy.lvl1) + ) + .map( + groupedHits => + groupedHits.map((hit) => { + const hierarchy = hit.hierarchy; + const objectID = hit.objectID; + const docsearchParent = hit.type !== 'lvl1' && + groupedHits.find( + (siblingItem) => + siblingItem.type === 'lvl1' && + siblingItem.hierarchy.lvl1 === + hit.hierarchy.lvl1 + ); + + return { + key: objectID, + hit, + hierarchy, + docsearchParent, + textValue: hit.type === 'content' ? hit[hit.type] : hierarchy[hit.type] + }; + } + )); + + sections.push({title, items: items.map((item) => item[0])}); + } + let newSuggestions = sections.map((section, index) => ({key: `${index}-${section.title}`, title: section.title, children: section.items})); + newSuggestions.push({ + key: 'algolia-footer', + title: 'Search by', + children: [ + { + key: 'algolia-footer-logo', + textValue: 'Algolia' + } + ] + }); + setSuggestions(newSuggestions); + setLoadingState(null); + }; + + let onInputChange = (query) => { + setSearchValue(query); + if (!query && predictions) { + setPredictions(null); + setSuggestions([]); + setLoadingState(null); + return; + } + setLoadingState('filtering'); + searchIndex + .search( + query, + searchOptions + ) + .then(updatePredictions); + }; + + let onSubmit = (value, key) => { + if (key === 'algolia-footer-logo') { + window.open('https://www.algolia.com/ref/docsearch/?utm_source=react-spectrum.adobe.com&utm_medium=referral&utm_content=powered_by&utm_campaign=docsearch', '_blank'); + searchAutocompleteRef.current.UNSAFE_getDOMNode().querySelector('[role="button"]').click(); + } else if (key) { + let prediction = predictions.find(prediction => key === prediction.objectID); + let url = prediction.url; + window.location.href = `${window.location.hostname === 'reactspectrum.blob.core.windows.net' ? window.location.href.replace(/(.+\/docs\/)(.+)/, '$1') : '/'}${url.replace('https://react-spectrum.adobe.com/', '')}`; + } + }; + + return ( + + + { + if (isOpen) { + document.body.classList.add(docsStyle['docsearch-open']); + } else { + document.body.classList.remove(docsStyle['docsearch-open']); + } + }} + onSubmit={onSubmit}> + {(section) => ( +
+ {(item) => ( + + {item.key === 'algolia-footer-logo' && ( + + + + + + )} + { + item.hierarchy && + item.hierarchy[item.hit.type] && + item.hit.type === 'lvl1' && ( + <> + { + section.title === 'Blog' || section.title === 'Releases' ? + : + + } + + + + {item.hit.content && ( + + + + )} + + ) + } + + { + item.hierarchy && item.hierarchy[item.hit.type] && + ( + item.hit.type === 'lvl2' || + item.hit.type === 'lvl3' || + item.hit.type === 'lvl4' || + item.hit.type === 'lvl5' || + item.hit.type === 'lvl6' + ) && ( + <> + + + + + + + + + ) + } + + { + item.hit && item.hit.type === 'content' && ( + <> + + + + + + + + + ) + } + + )} +
+ )} +
+
+
+ ); +} + +function Hash(props) { + return ( + + + + + + ); +} + +function groupBy(values, predicate = (value) => value) { + return values.reduce((accumulator, item) => { + const key = predicate(item); + + if (!Object.prototype.hasOwnProperty.call(accumulator, key)) { + accumulator[key] = []; + } + + // We limit each section to show 20 hits maximum. + // This acts as a frontend alternative to `distinct`. + if (accumulator[key].length < 21) { + accumulator[key].push(item); + } + + return accumulator; + }, {}); +} + +function getPropertyByPath(object, path) { + const parts = path.split('.'); + + return parts.reduce((prev, current) => { + if (prev?.[current]) { + return prev[current]; + } + return null; + }, object); +} + +function Snippet({ + hit, + attribute, + tagName = 'span', + ...rest +}) { + return React.createElement(tagName, { + ...rest, + dangerouslySetInnerHTML: { + __html: + DOMPurify.sanitize( + getPropertyByPath(hit, `_snippetResult.${attribute}.value`) || + getPropertyByPath(hit, attribute) + ) + } + }); +} + + diff --git a/packages/dev/docs/src/client.js b/packages/dev/docs/src/client.js index a0979629482..ccdd8ae688f 100644 --- a/packages/dev/docs/src/client.js +++ b/packages/dev/docs/src/client.js @@ -10,7 +10,8 @@ * governing permissions and limitations under the License. */ -import {ActionButton} from '@react-spectrum/button'; +import {ActionButton} from '@adobe/react-spectrum'; +import DocSearch from './DocSearch'; import docsStyle from './docs.css'; import {listen} from 'quicklink'; import React, {useEffect, useRef, useState} from 'react'; @@ -68,9 +69,9 @@ function Hamburger() { let hamburgerButtonRef = useRef(null); let onPress = (event) => { - let nav = document.querySelector('.' + docsStyle.nav); + let nav = document.querySelector(`.${docsStyle.nav}`); let main = document.querySelector('main'); - let themeSwitcher = event.target.parentElement.nextElementSibling; + let themeSwitcher = document.querySelector(`header.${docsStyle.pageHeader} > div:last-of-type`); nav.classList.toggle(docsStyle.visible); @@ -92,10 +93,10 @@ function Hamburger() { useEffect(() => { let mediaQueryList = window.matchMedia('(max-width: 1020px)'); - let nav = document.querySelector('.' + docsStyle.nav); + let nav = document.querySelector(`.${docsStyle.nav}`); let main = document.querySelector('main'); let hamburgerButton = hamburgerButtonRef.current; - let themeSwitcher = hamburgerRef.current.nextElementSibling; + let themeSwitcher = document.querySelector(`header.${docsStyle.pageHeader} > div:last-of-type`); let removeVisible = (isNotResponsive = false) => { setIsPressed(false); @@ -182,6 +183,7 @@ function Hamburger() { ReactDOM.render(<> + , document.querySelector('.' + docsStyle.pageHeader)); diff --git a/packages/dev/docs/src/docs.css b/packages/dev/docs/src/docs.css index b880e0d1cd8..e9dc58d1b76 100644 --- a/packages/dev/docs/src/docs.css +++ b/packages/dev/docs/src/docs.css @@ -150,6 +150,7 @@ html, body { --anatomy-gray-100: #f4f6fc; --anatomy-gray-75: #FDFDFE; --anatomy-gray-50: #FFFFFF; + --docsearch-logo-color: #5468FF; } .dark { @@ -167,6 +168,7 @@ html, body { --anatomy-gray-100: #141e35; --anatomy-gray-75: #0e1525; --anatomy-gray-50: #0c1220; + --docsearch-logo-color: #fff; .example { background-color: var(--spectrum-global-color-gray-100); } @@ -679,6 +681,7 @@ h2.sectionHeader { } .pageHeader { + gap: var(--spectrum-global-dimension-size-100); position: fixed; top: 0; display: inline-flex; @@ -693,6 +696,84 @@ h2.sectionHeader { [dir='rtl'] & { left: 0; } + + z-index: 1; +} + +.docSearchBoxThemeProvider { + height: var(--spectrum-global-dimension-size-400); + + .docSearchBox { + margin-inline-start: auto; + max-width: var(--spectrum-global-dimension-size-3000); + width: calc( + 100vw + - 2 * var(--spectrum-actionbutton-min-width, var(--spectrum-global-dimension-size-400)) + - 4 * var(--spectrum-alias-border-size-thin) + - 4 * var(--spectrum-global-dimension-size-100) + ); + min-width: calc( + var(--spectrum-global-dimension-size-225) + + 2 * var(--spectrum-global-dimension-size-150) + + 2 * var(--spectrum-alias-border-size-thin) + ); + } +} + +.docSearchBoxMark { + font-weight: bold; + background: var(--spectrum-alias-text-highlight-color); + color: var(--spectrum-global-color-blue-700); +} + +.docSearchItemIndent { + margin-inline-start: var(--spectrum-global-dimension-size-325); +} + +.docSearchFooter { + align-items: center; + display: flex; + flex-direction: row-reverse; + flex-shrink: 0; + justify-content: space-between; + + border-top-color: var(--spectrum-global-color-gray-300); + border-top-style: solid; + border-top-width: var(--spectrum-alias-border-size-thick); + margin-left: var(--spectrum-global-dimension-size-150); + margin-right: var(--spectrum-global-dimension-size-150); + margin-top: var(--spectrum-global-dimension-size-40); +} + +.docSearchLogo { + padding-top: var(--spectrum-global-dimension-size-75); + padding-bottom: var(--spectrum-global-dimension-size-75); +} + +.docSearchLogo a { + color: var(--spectrum-global-color-gray-700); + display: flex; + align-items: center; + text-decoration: none; +} + +.docSearchLogoLabel { + font-size: var(--spectrum-global-dimension-font-size-75); +} + +.docSearchLogo a:hover .docSearchLogoLabel, +.docSearchLogo a:focus .docSearchLogoLabel { + text-decoration: underline; +} + +.docSearchLogo svg { + color: var(--docsearch-logo-color); + margin-left: var(--spectrum-global-dimension-size-100); +} + +body.docsearch-open [data-testid="popover"] { + position: fixed !important; + top: 38px !important; } /* hamburger menu should be hidden so that it doesn't receive focus before the sidenav */ @@ -768,6 +849,26 @@ h2.sectionHeader { content: ' • '; } +/* Show line under page header and push main down to make room for search. */ +@media (max-width: 1780px) { + html, body { + scroll-padding-top: var(--spectrum-global-dimension-size-600); + } + + main { + margin-top: 48px; + } + + .pageHeader { + border-bottom: 1px solid var(--spectrum-global-color-gray-200); + background: var(--page-background); + z-index: 50; + display: flex; + justify-content: flex-end; + left: 256px; + } +} + @media (max-width: 750px) { .float { float: none; @@ -788,6 +889,24 @@ h2.sectionHeader { } } +@media (max-width: 375px) { + .docSearchBoxThemeProvider { + .docSearchBox { + max-width: unset; + > div, + > div > div { + min-width: calc( + var(--spectrum-global-dimension-size-150) + + var(--spectrum-global-dimension-size-225) + + var(--spectrum-global-dimension-size-65) + + var(--spectrum-global-dimension-size-350) + ); + width: 100%; + } + } + } +} + @define-mixin small-propTable { /* Collapse the prop table into a list with key/value pairs */ .propTable { @@ -905,8 +1024,11 @@ h2.sectionHeader { background: var(--page-background); z-index: 50; display: flex; + justify-content: space-between; + left: 0; width: 100%; } + .hamburgerButton { display: inline-flex; } diff --git a/yarn.lock b/yarn.lock index 3687643d38f..699115834f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -52,6 +52,110 @@ resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-ccx-workflow-icons/-/spectrum-css-ccx-workflow-icons-1.0.2.tgz#31a3a9f62d7411861dc7368cfa5fa8b42bacc608" integrity sha512-/Q9l1/6x1oZpHsiTIc6pJ4UrbufyKyn/6lfSskMjPsQ6l/jOsSMPR6yIUdCvt5GF4zjeFXqQvv13e6ywoqoGYg== +"@algolia/cache-browser-local-storage@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.14.1.tgz#a0b85a6c3fe3d5c49fca01b16f00b41bf38a918c" + integrity sha512-BBdibsPn3hLBajc/NRAtHEeoXsw+ziSGR/3bqRNB5puUuwKPQZSE2MaMVWSADnlc3KV3bEj4xsfKOVLJyfJSPQ== + dependencies: + "@algolia/cache-common" "4.14.1" + +"@algolia/cache-common@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.14.1.tgz#11d44a6442f83deb3629a04c20df8408088f6449" + integrity sha512-XhAzm0Sm3D3DuOWUyDoVSXZ/RjYMvI1rbki+QH4ODAVaHDWVhMhg3IJPv3gIbBQnEQdtPdBhsf2hyPxAu28E5w== + +"@algolia/cache-in-memory@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.14.1.tgz#68ede8520f054bc65938209b59962056ae5b56c7" + integrity sha512-fVUu7N1hYb/zZYfV9Krlij70NwS+8bQm5vmDJyfp0+9FjSjz2V7wj1CUxvaY8ZcgoBPj9ehQ8sRuqSM2m5OPww== + dependencies: + "@algolia/cache-common" "4.14.1" + +"@algolia/client-account@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.14.1.tgz#b92e091f698630c49ec7df48816ae75af3cbac40" + integrity sha512-Zm4+PN3bsBPhv1dKKwzBaRGzf0G1JcjjSTpE231L7Z7LsEDcFDW4E6L5ctwMz3SliSBeL/j1ghmaunJrZlkRIg== + dependencies: + "@algolia/client-common" "4.14.1" + "@algolia/client-search" "4.14.1" + "@algolia/transporter" "4.14.1" + +"@algolia/client-analytics@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.14.1.tgz#aca3436775f608a6141cc81899e1d75ef030efa2" + integrity sha512-EhZLR0ezBZx7ZGkwzj7OTvnI8j2Alyv1ByC0Mx48qh3KqRhVwMFm/Uf34zAv4Dum2PTFin41Y4smAvAypth9nQ== + dependencies: + "@algolia/client-common" "4.14.1" + "@algolia/client-search" "4.14.1" + "@algolia/requester-common" "4.14.1" + "@algolia/transporter" "4.14.1" + +"@algolia/client-common@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.14.1.tgz#2709bddf934a3545cd9feecc0591e9285fbed7c2" + integrity sha512-WDwziD7Rt1yCRDfONmeLOfh1Lt8uOy6Vn7dma171KOH9NN3q8yDQpOhPqdFOCz1j3GC1FfIZxaC0YEOIobZ2lg== + dependencies: + "@algolia/requester-common" "4.14.1" + "@algolia/transporter" "4.14.1" + +"@algolia/client-personalization@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.14.1.tgz#58f0b85b8f6d531e13877a099f54513ac2bec154" + integrity sha512-D4eeW7bTi769PWcEYZO+QiKuUXFOC5zK5Iy83Ey6FHqS7m5TXws5MP1rmETE018lTXeYq2NSHWp/F07fRRg0RA== + dependencies: + "@algolia/client-common" "4.14.1" + "@algolia/requester-common" "4.14.1" + "@algolia/transporter" "4.14.1" + +"@algolia/client-search@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.14.1.tgz#44bfc65b3e6939b725f8f97aad725593f2a4ad7f" + integrity sha512-K6XrdIIQq8a3o+kCedj5slUVzA1aKttae4mLzwnY0bS7tYduv1IQggi9Sz8gOG6/MMyKMB4IwYqr47t/0z4Vxw== + dependencies: + "@algolia/client-common" "4.14.1" + "@algolia/requester-common" "4.14.1" + "@algolia/transporter" "4.14.1" + +"@algolia/logger-common@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.14.1.tgz#acbd36b66e3b408f99cacfb581ad3bd28defcc28" + integrity sha512-58CK87wTjUWI1QNXc3nFDQ7EXBi28NoLufXE9sMjng2fAL1wPdyO+KFD8KTBoXOZnJWflPj5F7p6jLyGAfgvcQ== + +"@algolia/logger-console@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.14.1.tgz#7e7d7486d71ccfe38e63234626931083592149d2" + integrity sha512-not+VwH1Dx2B/BaN+4+4+YnGRBJ9lduNz2qbMCTxZ4yFHb+84j4ewHRPBTtEmibn7caVCPybdTKfHLQhimSBLQ== + dependencies: + "@algolia/logger-common" "4.14.1" + +"@algolia/requester-browser-xhr@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.14.1.tgz#9e683dc0916afae221bf946255a998b06830be78" + integrity sha512-mpH6QsFBbXjTy9+iU86Rcdt9LxS7GA/tWhGMr0+Ap8+4Za5+ELToz0PC7euVeVOcclgGGi7gbjOAmf6k8b10iA== + dependencies: + "@algolia/requester-common" "4.14.1" + +"@algolia/requester-common@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.14.1.tgz#b07ffa00ae0cf61442dcda71a3209051fed130d8" + integrity sha512-EbXBKrfYcX5/JJfaw7IZxhWlbUtjd5Chs+Alrfc4tutgRQn4dmImWS07n3iffwJcYdOWY1eRrnfBK5BwopuN5A== + +"@algolia/requester-node-http@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.14.1.tgz#5e5f4ff55deb5aa0e92f3105d77299de744b1471" + integrity sha512-/sbRqL9P8aVuYUG50BgpCbdJyyCS7fia+sQIx9d1DiGPO7hunwLaEyR4H7JDHc/PLKdVEPygJx3rnbJWix4Btg== + dependencies: + "@algolia/requester-common" "4.14.1" + +"@algolia/transporter@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.14.1.tgz#7eca8568ff710d9d1a7bbd3c1dafbbf44a6143f5" + integrity sha512-xbmoIqszFDOCCZqizBQ2TNHcGtjZX7EkJCzABsrokA0WqtfZzClFmtc+tZYgtEiyAfIF70alTegG19poQGdkvg== + dependencies: + "@algolia/cache-common" "4.14.1" + "@algolia/logger-common" "4.14.1" + "@algolia/requester-common" "4.14.1" + "@babel/cli@^7.12.10": version "7.12.10" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.12.10.tgz#67a1015b1cd505bde1696196febf910c4c339a48" @@ -5014,6 +5118,26 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv json-schema-traverse "^0.4.1" uri-js "^4.2.2" +algoliasearch@^4.14.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.14.1.tgz#7f24cabd264f8294b461d108e1603e673571e806" + integrity sha512-ZWqnbsGUgU03/IyG995pMCc+EmNVDA/4c9ntr8B0dWQwFqazOQ4ErvKZxarbgSNmyPo/eZcVsTb0bNplJMttGQ== + dependencies: + "@algolia/cache-browser-local-storage" "4.14.1" + "@algolia/cache-common" "4.14.1" + "@algolia/cache-in-memory" "4.14.1" + "@algolia/client-account" "4.14.1" + "@algolia/client-analytics" "4.14.1" + "@algolia/client-common" "4.14.1" + "@algolia/client-personalization" "4.14.1" + "@algolia/client-search" "4.14.1" + "@algolia/logger-common" "4.14.1" + "@algolia/logger-console" "4.14.1" + "@algolia/requester-browser-xhr" "4.14.1" + "@algolia/requester-common" "4.14.1" + "@algolia/requester-node-http" "4.14.1" + "@algolia/transporter" "4.14.1" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -8502,6 +8626,11 @@ dompurify@2.3.8: resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.8.tgz#224fe9ae57d7ebd9a1ae1ac18c1c1ca3f532226f" integrity sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw== +dompurify@^2.3.10: + version "2.3.10" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.10.tgz#901f7390ffe16a91a5a556b94043314cd4850385" + integrity sha512-o7Fg/AgC7p/XpKjf/+RC3Ok6k4St5F7Q6q6+Nnm3p2zGWioAY6dh0CbbuwOhH2UcSzKsdniE/YnE2/92JcsA+g== + domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"