Merge branch 'html-parse' into 'main'
StatusContent: parse links into components See merge request soapbox-pub/soapbox!2812
This commit is contained in:
commit
0206316acd
8 changed files with 183 additions and 103 deletions
|
@ -110,6 +110,7 @@
|
|||
"escape-html": "^1.0.3",
|
||||
"exifr": "^7.1.3",
|
||||
"graphemesplit": "^2.4.4",
|
||||
"html-react-parser": "^4.2.2",
|
||||
"http-link-header": "^1.0.2",
|
||||
"immer": "^10.0.0",
|
||||
"immutable": "^4.2.1",
|
||||
|
|
15
src/components/hashtag-link.tsx
Normal file
15
src/components/hashtag-link.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
|
||||
import Link from './link';
|
||||
|
||||
interface IHashtagLink {
|
||||
hashtag: string;
|
||||
}
|
||||
|
||||
const HashtagLink: React.FC<IHashtagLink> = ({ hashtag }) => (
|
||||
<Link to={`/tags/${hashtag}`}>
|
||||
#{hashtag}
|
||||
</Link>
|
||||
);
|
||||
|
||||
export default HashtagLink;
|
38
src/components/mention.tsx
Normal file
38
src/components/mention.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { isPubkey } from 'soapbox/utils/nostr';
|
||||
|
||||
import { Tooltip } from './ui';
|
||||
|
||||
import type { Mention as MentionEntity } from 'soapbox/schemas';
|
||||
|
||||
interface IMention {
|
||||
mention: Pick<MentionEntity, 'acct' | 'username'>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/** Mention for display in post content and the composer. */
|
||||
const Mention: React.FC<IMention> = ({ mention: { acct, username }, disabled }) => {
|
||||
const handleClick: React.MouseEventHandler = (e) => {
|
||||
if (disabled) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip text={`@${acct}`}>
|
||||
<Link
|
||||
to={`/@${acct}`}
|
||||
className='text-primary-600 hover:underline dark:text-accent-blue'
|
||||
onClick={handleClick}
|
||||
dir='ltr'
|
||||
>
|
||||
@{isPubkey(username) ? username.slice(0, 8) : username}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default Mention;
|
|
@ -1,18 +1,20 @@
|
|||
import clsx from 'clsx';
|
||||
import parse, { Element, type HTMLReactParserOptions, domToReact } from 'html-react-parser';
|
||||
import React, { useState, useRef, useLayoutEffect, useMemo } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { onlyEmoji as isOnlyEmoji } from 'soapbox/utils/rich-content';
|
||||
|
||||
import { getTextDirection } from '../utils/rtl';
|
||||
|
||||
import HashtagLink from './hashtag-link';
|
||||
import Markup from './markup';
|
||||
import Mention from './mention';
|
||||
import Poll from './polls/poll';
|
||||
|
||||
import type { Sizes } from 'soapbox/components/ui/text/text';
|
||||
import type { Status, Mention } from 'soapbox/types/entities';
|
||||
import type { Status } from 'soapbox/types/entities';
|
||||
|
||||
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
|
||||
const BIG_EMOJI_LIMIT = 10;
|
||||
|
@ -45,66 +47,11 @@ const StatusContent: React.FC<IStatusContent> = ({
|
|||
translatable,
|
||||
textSize = 'md',
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [onlyEmoji, setOnlyEmoji] = useState(false);
|
||||
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onMentionClick = (mention: Mention, e: MouseEvent) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
history.push(`/@${mention.acct}`);
|
||||
}
|
||||
};
|
||||
|
||||
const onHashtagClick = (hashtag: string, e: MouseEvent) => {
|
||||
hashtag = hashtag.replace(/^#/, '').toLowerCase();
|
||||
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
history.push(`/tags/${hashtag}`);
|
||||
}
|
||||
};
|
||||
|
||||
/** For regular links, just stop propogation */
|
||||
const onLinkClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const updateStatusLinks = () => {
|
||||
if (!node.current) return;
|
||||
|
||||
const links = node.current.querySelectorAll('a');
|
||||
|
||||
links.forEach(link => {
|
||||
// Skip already processed
|
||||
if (link.classList.contains('status-link')) return;
|
||||
|
||||
// Add attributes
|
||||
link.classList.add('status-link');
|
||||
link.setAttribute('rel', 'nofollow noopener');
|
||||
link.setAttribute('target', '_blank');
|
||||
|
||||
const mention = status.mentions.find(mention => link.href === `${mention.url}`);
|
||||
|
||||
// Add event listeners on mentions and hashtags
|
||||
if (mention) {
|
||||
link.addEventListener('click', onMentionClick.bind(link, mention), false);
|
||||
link.setAttribute('title', mention.acct);
|
||||
link.setAttribute('dir', 'ltr');
|
||||
} else if (link.textContent?.charAt(0) === '#' || (link.previousSibling?.textContent?.charAt(link.previousSibling.textContent.length - 1) === '#')) {
|
||||
link.addEventListener('click', onHashtagClick.bind(link, link.text), false);
|
||||
} else {
|
||||
link.setAttribute('title', link.href);
|
||||
link.addEventListener('click', onLinkClick.bind(link), false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const maybeSetCollapsed = (): void => {
|
||||
if (!node.current) return;
|
||||
|
||||
|
@ -127,7 +74,6 @@ const StatusContent: React.FC<IStatusContent> = ({
|
|||
useLayoutEffect(() => {
|
||||
maybeSetCollapsed();
|
||||
maybeSetOnlyEmoji();
|
||||
updateStatusLinks();
|
||||
});
|
||||
|
||||
const parsedHtml = useMemo((): string => {
|
||||
|
@ -142,7 +88,48 @@ const StatusContent: React.FC<IStatusContent> = ({
|
|||
|
||||
const baseClassName = 'text-gray-900 dark:text-gray-100 break-words text-ellipsis overflow-hidden relative focus:outline-none';
|
||||
|
||||
const content = { __html: parsedHtml };
|
||||
const options: HTMLReactParserOptions = {
|
||||
replace(domNode) {
|
||||
if (domNode instanceof Element && ['script', 'iframe'].includes(domNode.name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (domNode instanceof Element && domNode.name === 'a') {
|
||||
const classes = domNode.attribs.class?.split(' ');
|
||||
|
||||
if (classes?.includes('mention')) {
|
||||
const mention = status.mentions.find(({ url }) => domNode.attribs.href === url);
|
||||
if (mention) {
|
||||
return <Mention mention={mention} />;
|
||||
}
|
||||
}
|
||||
|
||||
if (classes?.includes('hashtag')) {
|
||||
const child = domToReact(domNode.children);
|
||||
const hashtag = typeof child === 'string' ? child.replace(/^#/, '') : undefined;
|
||||
if (hashtag) {
|
||||
return <HashtagLink hashtag={hashtag} />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<a
|
||||
{...domNode.attribs}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
rel='nofollow noopener'
|
||||
target='_blank'
|
||||
title={domNode.attribs.href}
|
||||
>
|
||||
{domToReact(domNode.children, options)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const content = parse(parsedHtml, options);
|
||||
|
||||
const direction = getTextDirection(status.search_index);
|
||||
const className = clsx(baseClassName, {
|
||||
'cursor-pointer': onClick,
|
||||
|
@ -159,10 +146,11 @@ const StatusContent: React.FC<IStatusContent> = ({
|
|||
key='content'
|
||||
className={className}
|
||||
direction={direction}
|
||||
dangerouslySetInnerHTML={content}
|
||||
lang={status.language || undefined}
|
||||
size={textSize}
|
||||
/>,
|
||||
>
|
||||
{content}
|
||||
</Markup>,
|
||||
];
|
||||
|
||||
if (collapsed) {
|
||||
|
@ -185,10 +173,11 @@ const StatusContent: React.FC<IStatusContent> = ({
|
|||
'leading-normal big-emoji': onlyEmoji,
|
||||
})}
|
||||
direction={direction}
|
||||
dangerouslySetInnerHTML={content}
|
||||
lang={status.language || undefined}
|
||||
size={textSize}
|
||||
/>,
|
||||
>
|
||||
{content}
|
||||
</Markup>,
|
||||
];
|
||||
|
||||
if (status.poll && typeof status.poll === 'string') {
|
||||
|
|
|
@ -52,7 +52,6 @@ interface IComposeEditor {
|
|||
const theme: InitialConfigType['theme'] = {
|
||||
emoji: 'select-none',
|
||||
hashtag: 'hover:underline text-primary-600 dark:text-accent-blue hover:text-primary-800 dark:hover:text-accent-blue',
|
||||
mention: 'hover:underline text-primary-600 dark:text-accent-blue hover:text-primary-800 dark:hover:text-accent-blue select-none',
|
||||
link: 'hover:underline text-primary-600 dark:text-accent-blue hover:text-primary-800 dark:hover:text-accent-blue',
|
||||
text: {
|
||||
bold: 'font-bold',
|
||||
|
|
|
@ -4,12 +4,10 @@
|
|||
* LICENSE file in the /src/features/compose/editor directory.
|
||||
*/
|
||||
|
||||
import { addClassNamesToElement } from '@lexical/utils';
|
||||
import { $applyNodeReplacement, DecoratorNode } from 'lexical';
|
||||
import React from 'react';
|
||||
|
||||
import { Tooltip } from 'soapbox/components/ui';
|
||||
import { isPubkey } from 'soapbox/utils/nostr';
|
||||
import Mention from 'soapbox/components/mention';
|
||||
|
||||
import type {
|
||||
EditorConfig,
|
||||
|
@ -18,34 +16,33 @@ import type {
|
|||
SerializedLexicalNode,
|
||||
Spread,
|
||||
} from 'lexical';
|
||||
import type { Mention as MentionEntity } from 'soapbox/schemas';
|
||||
|
||||
type SerializedMentionNode = Spread<{
|
||||
acct: string;
|
||||
mention: MentionEntity;
|
||||
type: 'mention';
|
||||
version: 1;
|
||||
}, SerializedLexicalNode>;
|
||||
|
||||
class MentionNode extends DecoratorNode<JSX.Element> {
|
||||
|
||||
__acct: string;
|
||||
__mention: MentionEntity;
|
||||
|
||||
static getType(): string {
|
||||
return 'mention';
|
||||
}
|
||||
|
||||
static clone(node: MentionNode): MentionNode {
|
||||
return new MentionNode(node.__acct, node.__key);
|
||||
return new MentionNode(node.__mention, node.__key);
|
||||
}
|
||||
|
||||
constructor(acct: string, key?: NodeKey) {
|
||||
constructor(mention: MentionEntity, key?: NodeKey) {
|
||||
super(key);
|
||||
this.__acct = acct;
|
||||
this.__mention = mention;
|
||||
}
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const span = document.createElement('span');
|
||||
addClassNamesToElement(span, config.theme.mention);
|
||||
return span;
|
||||
return document.createElement('span');
|
||||
}
|
||||
|
||||
updateDOM(): false {
|
||||
|
@ -53,20 +50,20 @@ class MentionNode extends DecoratorNode<JSX.Element> {
|
|||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedMentionNode): MentionNode {
|
||||
const node = $createMentionNode(serializedNode.acct);
|
||||
const node = $createMentionNode(serializedNode.mention);
|
||||
return node;
|
||||
}
|
||||
|
||||
exportJSON(): SerializedMentionNode {
|
||||
return {
|
||||
type: 'mention',
|
||||
acct: this.__acct,
|
||||
mention: this.__mention,
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
getTextContent(): string {
|
||||
return `@${this.__acct}`;
|
||||
return `@${this.__mention.acct}`;
|
||||
}
|
||||
|
||||
canInsertTextBefore(): boolean {
|
||||
|
@ -78,26 +75,15 @@ class MentionNode extends DecoratorNode<JSX.Element> {
|
|||
}
|
||||
|
||||
decorate(): JSX.Element {
|
||||
const acct = this.__acct;
|
||||
const username = acct.split('@')[0];
|
||||
|
||||
return (
|
||||
<Tooltip text={`@${acct}`}>
|
||||
<button
|
||||
className='text-accent-blue'
|
||||
type='button'
|
||||
dir='ltr'
|
||||
>
|
||||
@{isPubkey(username) ? username.slice(0, 8) : username}
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Mention mention={this.__mention} disabled />
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function $createMentionNode(acct: string): MentionNode {
|
||||
const node = new MentionNode(acct);
|
||||
function $createMentionNode(mention: MentionEntity): MentionNode {
|
||||
const node = new MentionNode(mention);
|
||||
return $applyNodeReplacement(node);
|
||||
}
|
||||
|
||||
|
|
|
@ -327,8 +327,8 @@ const AutosuggestPlugin = ({
|
|||
node.setTextContent(`${suggestion} `);
|
||||
node.select();
|
||||
} else {
|
||||
const acct = selectAccount(getState(), suggestion)!.acct;
|
||||
replaceMatch($createMentionNode(acct));
|
||||
const account = selectAccount(getState(), suggestion)!;
|
||||
replaceMatch($createMentionNode(account));
|
||||
}
|
||||
|
||||
dispatch(clearComposeSuggestions(composeId));
|
||||
|
|
70
yarn.lock
70
yarn.lock
|
@ -4027,6 +4027,13 @@ domexception@^4.0.0:
|
|||
dependencies:
|
||||
webidl-conversions "^7.0.0"
|
||||
|
||||
domhandler@5.0.3, domhandler@^5.0.2, domhandler@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
|
||||
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
|
||||
domhandler@^4.2.0, domhandler@^4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
|
||||
|
@ -4034,13 +4041,6 @@ domhandler@^4.2.0, domhandler@^4.3.1:
|
|||
dependencies:
|
||||
domelementtype "^2.2.0"
|
||||
|
||||
domhandler@^5.0.2, domhandler@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
|
||||
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
|
||||
domutils@^2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
||||
|
@ -4050,7 +4050,7 @@ domutils@^2.8.0:
|
|||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
|
||||
domutils@^3.0.1:
|
||||
domutils@^3.0.1, domutils@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
|
||||
integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==
|
||||
|
@ -4148,7 +4148,7 @@ entities@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
|
||||
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
|
||||
|
||||
entities@^4.2.0, entities@^4.4.0:
|
||||
entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
|
||||
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
|
||||
|
@ -5079,6 +5079,14 @@ hosted-git-info@^4.0.1:
|
|||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
html-dom-parser@4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-4.0.0.tgz#dc382fbbc9306f8c9b5aae4e3f2822e113a48709"
|
||||
integrity sha512-TUa3wIwi80f5NF8CVWzkopBVqVAtlawUzJoLwVLHns0XSJGynss4jiY0mTWpiDOsuyw+afP+ujjMgRh9CoZcXw==
|
||||
dependencies:
|
||||
domhandler "5.0.3"
|
||||
htmlparser2 "9.0.0"
|
||||
|
||||
html-encoding-sniffer@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
|
||||
|
@ -5099,11 +5107,31 @@ html-minifier-terser@^6.1.0:
|
|||
relateurl "^0.2.7"
|
||||
terser "^5.10.0"
|
||||
|
||||
html-react-parser@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-4.2.2.tgz#91f1cc2138bc069d65cbd8b9d97b1e71ed423300"
|
||||
integrity sha512-lh0wEGISnFZEAmvQqK4xc0duFMUh/m9YYyAhFursWxdtNv+hCZge0kj1y4wep6qPB5Zm33L+2/P6TcGWAJJbjA==
|
||||
dependencies:
|
||||
domhandler "5.0.3"
|
||||
html-dom-parser "4.0.0"
|
||||
react-property "2.0.0"
|
||||
style-to-js "1.1.4"
|
||||
|
||||
html-tags@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
|
||||
integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
|
||||
|
||||
htmlparser2@9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.0.0.tgz#e431142b7eeb1d91672742dea48af8ac7140cddb"
|
||||
integrity sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ==
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
domhandler "^5.0.3"
|
||||
domutils "^3.1.0"
|
||||
entities "^4.5.0"
|
||||
|
||||
http-cache-semantics@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
||||
|
@ -5232,6 +5260,11 @@ ini@^1.3.5:
|
|||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
inline-style-parser@0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
|
||||
integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
|
||||
|
||||
internal-slot@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
|
||||
|
@ -7275,6 +7308,11 @@ react-popper@^2.2.5, react-popper@^2.3.0:
|
|||
react-fast-compare "^3.0.1"
|
||||
warning "^4.0.2"
|
||||
|
||||
react-property@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.0.tgz#2156ba9d85fa4741faf1918b38efc1eae3c6a136"
|
||||
integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==
|
||||
|
||||
react-redux@^8.0.0:
|
||||
version "8.0.5"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
|
||||
|
@ -8103,6 +8141,20 @@ style-search@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
|
||||
integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=
|
||||
|
||||
style-to-js@1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.4.tgz#5fa07a181ec3ca354d699edf442549e0ac61ed32"
|
||||
integrity sha512-zEeU3vy9xL/hdLBFmzqjhm+2vJ1Y35V0ctDeB2sddsvN1856OdMZUCOOfKUn3nOjjEKr6uLhOnY4CrX6gLDRrA==
|
||||
dependencies:
|
||||
style-to-object "0.4.2"
|
||||
|
||||
style-to-object@0.4.2:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.4.2.tgz#a8247057111dea8bd3b8a1a66d2d0c9cf9218a54"
|
||||
integrity sha512-1JGpfPB3lo42ZX8cuPrheZbfQ6kqPPnPHlKMyeRYtfKD+0jG+QsXgXN57O/dvJlzlB2elI6dGmrPnl5VPQFPaA==
|
||||
dependencies:
|
||||
inline-style-parser "0.1.1"
|
||||
|
||||
stylehacks@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.0.0.tgz#9fdd7c217660dae0f62e14d51c89f6c01b3cb738"
|
||||
|
|
Loading…
Reference in a new issue