mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-02-13 17:10:06 +00:00
render custom html with new styles
This commit is contained in:
parent
e5dd7d3011
commit
1152d1d563
|
@ -20,6 +20,9 @@ module.exports = {
|
|||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
"globals": {
|
||||
JSX: "readonly"
|
||||
},
|
||||
plugins: [
|
||||
'react',
|
||||
'@typescript-eslint'
|
||||
|
|
76
package-lock.json
generated
76
package-lock.json
generated
|
@ -30,12 +30,13 @@
|
|||
"focus-trap-react": "10.0.2",
|
||||
"folds": "1.3.0",
|
||||
"formik": "2.2.9",
|
||||
"html-react-parser": "3.0.4",
|
||||
"html-react-parser": "4.2.0",
|
||||
"immer": "9.0.16",
|
||||
"is-hotkey": "0.2.0",
|
||||
"jotai": "1.12.0",
|
||||
"katex": "0.16.4",
|
||||
"linkify-html": "4.0.2",
|
||||
"linkify-react": "4.1.1",
|
||||
"linkifyjs": "4.0.2",
|
||||
"matrix-js-sdk": "24.1.0",
|
||||
"millify": "6.1.0",
|
||||
|
@ -2364,13 +2365,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
|
||||
"integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.1"
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
|
@ -2409,9 +2410,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
|
||||
"integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
|
@ -3595,23 +3596,41 @@
|
|||
}
|
||||
},
|
||||
"node_modules/html-dom-parser": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-3.1.2.tgz",
|
||||
"integrity": "sha512-mLTtl3pVn3HnqZSZzW3xVs/mJAKrG1yIw3wlp+9bdoZHHLaBRvELdpfShiPVLyjPypq1Fugv2KMDoGHW4lVXnw==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-4.0.0.tgz",
|
||||
"integrity": "sha512-TUa3wIwi80f5NF8CVWzkopBVqVAtlawUzJoLwVLHns0XSJGynss4jiY0mTWpiDOsuyw+afP+ujjMgRh9CoZcXw==",
|
||||
"dependencies": {
|
||||
"domhandler": "5.0.3",
|
||||
"htmlparser2": "8.0.1"
|
||||
"htmlparser2": "9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-dom-parser/node_modules/htmlparser2": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.0.0.tgz",
|
||||
"integrity": "sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ==",
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.1.0",
|
||||
"entities": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-react-parser": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-3.0.4.tgz",
|
||||
"integrity": "sha512-va68PSmC7uA6PbOEc9yuw5Mu3OHPXmFKUpkLGvUPdTuNrZ0CJZk1s/8X/FaHjswK/6uZghu2U02tJjussT8+uw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-gzU55AS+FI6qD7XaKe5BLuLFM2Xw0/LodfMWZlxV9uOHe7LCD5Lukx/EgYuBI3c0kLu0XlgFXnSzO0qUUn3Vrg==",
|
||||
"dependencies": {
|
||||
"domhandler": "5.0.3",
|
||||
"html-dom-parser": "3.1.2",
|
||||
"html-dom-parser": "4.0.0",
|
||||
"react-property": "2.0.0",
|
||||
"style-to-js": "1.1.1"
|
||||
"style-to-js": "1.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "0.14 || 15 || 16 || 17 || 18"
|
||||
|
@ -4202,6 +4221,15 @@
|
|||
"linkifyjs": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/linkify-react": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.1.1.tgz",
|
||||
"integrity": "sha512-2K9Y1cUdvq40dFWqCJ//X+WP19nlzIVITFGI93RjLnA0M7KbnxQ/ffC3AZIZaEIrLangF9Hjt3i0GQ9/anEG5A==",
|
||||
"peerDependencies": {
|
||||
"linkifyjs": "^4.0.0",
|
||||
"react": ">= 15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/linkifyjs": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.0.2.tgz",
|
||||
|
@ -5437,17 +5465,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/style-to-js": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.1.tgz",
|
||||
"integrity": "sha512-RJ18Z9t2B02sYhZtfWKQq5uplVctgvjTfLWT7+Eb1zjUjIrWzX5SdlkwLGQozrqarTmEzJJ/YmdNJCUNI47elg==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.3.tgz",
|
||||
"integrity": "sha512-zKI5gN/zb7LS/Vm0eUwjmjrXWw8IMtyA8aPBJZdYiQTXj4+wQ3IucOLIOnF7zCHxvW8UhIGh/uZh/t9zEHXNTQ==",
|
||||
"dependencies": {
|
||||
"style-to-object": "0.3.0"
|
||||
"style-to-object": "0.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/style-to-object": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",
|
||||
"integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==",
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz",
|
||||
"integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==",
|
||||
"dependencies": {
|
||||
"inline-style-parser": "0.1.1"
|
||||
}
|
||||
|
|
|
@ -40,12 +40,13 @@
|
|||
"focus-trap-react": "10.0.2",
|
||||
"folds": "1.3.0",
|
||||
"formik": "2.2.9",
|
||||
"html-react-parser": "3.0.4",
|
||||
"html-react-parser": "4.2.0",
|
||||
"immer": "9.0.16",
|
||||
"is-hotkey": "0.2.0",
|
||||
"jotai": "1.12.0",
|
||||
"katex": "0.16.4",
|
||||
"linkify-html": "4.0.2",
|
||||
"linkify-react": "4.1.1",
|
||||
"linkifyjs": "4.0.2",
|
||||
"matrix-js-sdk": "24.1.0",
|
||||
"millify": "6.1.0",
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Scroll, Text } from 'folds';
|
|||
import React from 'react';
|
||||
import { RenderElementProps, RenderLeafProps, useFocused, useSelected } from 'slate-react';
|
||||
|
||||
import * as css from './Elements.css';
|
||||
import * as css from '../../styles/CustomHtml.css';
|
||||
import { EmoticonElement, LinkElement, MentionElement } from './slate';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
|
||||
|
@ -145,7 +145,13 @@ export function RenderElement({ attributes, element, children }: RenderElementPr
|
|||
case BlockType.CodeBlock:
|
||||
return (
|
||||
<Text as="pre" className={css.CodeBlock} {...attributes}>
|
||||
<Scroll direction="Horizontal" variant="Warning" size="300" visibility="Hover" hideTrack>
|
||||
<Scroll
|
||||
direction="Horizontal"
|
||||
variant="Secondary"
|
||||
size="300"
|
||||
visibility="Hover"
|
||||
hideTrack
|
||||
>
|
||||
<div className={css.CodeBlockInternal}>{children}</div>
|
||||
</Scroll>
|
||||
</Text>
|
||||
|
@ -242,7 +248,7 @@ export function RenderLeaf({ attributes, leaf, children }: RenderLeafProps) {
|
|||
);
|
||||
if (leaf.spoiler)
|
||||
child = (
|
||||
<span className={css.Spoiler} {...attributes}>
|
||||
<span className={css.Spoiler()} {...attributes}>
|
||||
<InlineChromiumBugfix />
|
||||
{child}
|
||||
</span>
|
||||
|
|
|
@ -4,6 +4,7 @@ import React, {
|
|||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
@ -16,18 +17,21 @@ import {
|
|||
Room,
|
||||
RoomEvent,
|
||||
} from 'matrix-js-sdk';
|
||||
import parse from 'html-react-parser';
|
||||
import parse, { HTMLReactParserOptions } from 'html-react-parser';
|
||||
import to from 'await-to-js';
|
||||
import { Box, Scroll, Text, color, config } from 'folds';
|
||||
import Linkify from 'linkify-react';
|
||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
import { sanitizeCustomHtml } from '../../../util/sanitize';
|
||||
import { sanitizeCustomHtml } from '../../utils/sanitize';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useVirtualPaginator, ItemRange } from '../../hooks/useVirtualPaginator';
|
||||
import { useAlive } from '../../hooks/useAlive';
|
||||
import { scrollToBottom } from '../../utils/dom';
|
||||
import { CompactMessagePlaceholder } from '../../components/message';
|
||||
import { CompactMessage } from '../../components/message/CompactMessage';
|
||||
import { LINKIFY_OPTS, getReactCustomHtmlParser } from '../../plugins/react-custom-html-parser';
|
||||
import { getMemberDisplayName } from '../../utils/room';
|
||||
|
||||
export const getLiveTimeline = (room: Room): EventTimeline =>
|
||||
room.getUnfilteredTimelineSet().getLiveTimeline();
|
||||
|
@ -171,6 +175,11 @@ export function RoomTimeline({ room, eventId }: RoomTimelineProps) {
|
|||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const eventArriveCountRef = useRef(0);
|
||||
|
||||
const htmlReactParserOptions = useMemo<HTMLReactParserOptions>(
|
||||
() => getReactCustomHtmlParser(mx, room),
|
||||
[mx, room]
|
||||
);
|
||||
|
||||
const [timeline, setTimeline] = useState<Timeline>(() => {
|
||||
const linkedTimelines = eventId ? [] : getLinkedTimelines(getLiveTimeline(room));
|
||||
const evLength = getTimelinesTotalLength(linkedTimelines);
|
||||
|
@ -275,33 +284,51 @@ export function RoomTimeline({ room, eventId }: RoomTimelineProps) {
|
|||
const { body } = mEvent.getContent();
|
||||
if (!body) return null;
|
||||
const customBody = mEvent.getContent().formatted_body;
|
||||
const senderId = mEvent.getSender() ?? '';
|
||||
|
||||
return (
|
||||
<CompactMessage key={mEvent.getId()} data-message-item={item}>
|
||||
<Box
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top: config.space.S100,
|
||||
maxWidth: 170,
|
||||
width: '100%',
|
||||
}}
|
||||
gap="200"
|
||||
shrink="No"
|
||||
justifyContent="SpaceBetween"
|
||||
alignItems="Baseline"
|
||||
>
|
||||
<Text size="T200" priority="300">
|
||||
<Text style={{ flexShrink: 0 }} size="T200" priority="300">
|
||||
{new Date(mEvent.getTs()).toLocaleTimeString()}
|
||||
</Text>
|
||||
<Text
|
||||
truncate
|
||||
style={{
|
||||
maxWidth: 120,
|
||||
color: colorMXID(mEvent.getSender()),
|
||||
color: colorMXID(senderId),
|
||||
}}
|
||||
>
|
||||
<b>{getMxIdLocalPart(mEvent?.getSender() ?? '')}</b>
|
||||
<b>{getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId)}</b>
|
||||
</Text>
|
||||
</Box>
|
||||
<Box
|
||||
grow="Yes"
|
||||
direction="Column"
|
||||
// style={{
|
||||
// borderRadius: 8,
|
||||
// padding: 8,
|
||||
// backgroundColor: color.SurfaceVariant.Container,
|
||||
// }}
|
||||
>
|
||||
<Text as="div" style={{ whiteSpace: !customBody ? 'pre-wrap' : 'initial' }}>
|
||||
{customBody ? (
|
||||
parse(sanitizeCustomHtml(customBody), htmlReactParserOptions)
|
||||
) : (
|
||||
<Linkify options={LINKIFY_OPTS}>{body}</Linkify>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text as="div">
|
||||
{customBody ? parse(sanitizeCustomHtml(mx, customBody)) : body}
|
||||
</Text>
|
||||
</CompactMessage>
|
||||
);
|
||||
})}
|
||||
|
|
223
src/app/plugins/react-custom-html-parser.tsx
Normal file
223
src/app/plugins/react-custom-html-parser.tsx
Normal file
|
@ -0,0 +1,223 @@
|
|||
/* eslint-disable jsx-a11y/alt-text */
|
||||
import React from 'react';
|
||||
import {
|
||||
Element,
|
||||
Text as DOMText,
|
||||
HTMLReactParserOptions,
|
||||
attributesToProps,
|
||||
domToReact,
|
||||
} from 'html-react-parser';
|
||||
import { MatrixClient, Room } from 'matrix-js-sdk';
|
||||
import classNames from 'classnames';
|
||||
import { Scroll, Text } from 'folds';
|
||||
import { Opts as LinkifyOpts } from 'linkifyjs';
|
||||
import Linkify from 'linkify-react';
|
||||
import * as css from '../styles/CustomHtml.css';
|
||||
import { getMxIdLocalPart, getRoomWithCanonicalAlias } from '../utils/matrix';
|
||||
import { getMemberDisplayName } from '../utils/room';
|
||||
|
||||
export const LINKIFY_OPTS: LinkifyOpts = {
|
||||
attributes: {
|
||||
target: '_blank',
|
||||
rel: 'noreferrer noopener',
|
||||
},
|
||||
validate: {
|
||||
url: (value) => /^(https|http|ftp|mailto|magnet)?:/.test(value),
|
||||
},
|
||||
};
|
||||
|
||||
export const getReactCustomHtmlParser = (mx: MatrixClient, room: Room): HTMLReactParserOptions => {
|
||||
const opts: HTMLReactParserOptions = {
|
||||
replace: (domNode) => {
|
||||
if (domNode instanceof Element && 'name' in domNode) {
|
||||
const { name, attribs, children, parent } = domNode;
|
||||
const props = attributesToProps(attribs);
|
||||
|
||||
if (name === 'h1') {
|
||||
return (
|
||||
<Text className={css.Heading} size="H2" {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'h2') {
|
||||
return (
|
||||
<Text className={css.Heading} size="H3" {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'h3') {
|
||||
return (
|
||||
<Text className={css.Heading} size="H4" {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'h4') {
|
||||
return (
|
||||
<Text className={css.Heading} size="H4" {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'h5') {
|
||||
return (
|
||||
<Text className={css.Heading} size="H5" {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'h6') {
|
||||
return (
|
||||
<Text className={css.Heading} size="H6" {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'p') {
|
||||
return (
|
||||
<Text className={classNames(css.Paragraph, css.MarginSpaced)} size="Inherit" {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'pre') {
|
||||
return (
|
||||
<Text as="pre" className={css.CodeBlock} {...props}>
|
||||
<Scroll
|
||||
direction="Horizontal"
|
||||
variant="Secondary"
|
||||
size="300"
|
||||
visibility="Hover"
|
||||
hideTrack
|
||||
>
|
||||
<div className={css.CodeBlockInternal}>{domToReact(children, opts)}</div>
|
||||
</Scroll>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'blockquote') {
|
||||
return (
|
||||
<Text size="Inherit" as="blockquote" className={css.BlockQuote} {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'ul') {
|
||||
return (
|
||||
<ul className={css.List} {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
if (name === 'ol') {
|
||||
return (
|
||||
<ol className={css.List} {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'code' && parent && 'name' in parent && parent.name !== 'pre') {
|
||||
return (
|
||||
<code className={css.Code} {...props}>
|
||||
{domToReact(children, opts)}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'a') {
|
||||
const mention = decodeURIComponent(props.href).match(
|
||||
/^https?:\/\/matrix.to\/#\/((@|#|!).+:[^?/]+)/
|
||||
);
|
||||
if (mention) {
|
||||
// convert mention link to pill
|
||||
const mentionId = mention[1];
|
||||
const mentionPrefix = mention[2];
|
||||
if (mentionPrefix === '#' || mentionPrefix === '!') {
|
||||
const mentionRoom =
|
||||
mentionPrefix === '#'
|
||||
? getRoomWithCanonicalAlias(mx, mentionId)
|
||||
: mx.getRoom(mentionId);
|
||||
const mentionName = mentionRoom?.name;
|
||||
|
||||
const mentionDisplayName =
|
||||
mentionName && (mentionName.startsWith('#') ? mentionName : `#${mentionName}`);
|
||||
return (
|
||||
<span
|
||||
className={css.Mention({
|
||||
highlight: room.roomId === (mentionRoom?.roomId ?? mentionId),
|
||||
})}
|
||||
data-mx-pill={mentionId}
|
||||
{...props}
|
||||
>
|
||||
{mentionDisplayName ?? mentionId}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (mentionPrefix === '@')
|
||||
return (
|
||||
<span
|
||||
className={css.Mention({
|
||||
highlight: mx.getUserId() === mentionId,
|
||||
})}
|
||||
data-mx-pill={mentionId}
|
||||
{...props}
|
||||
>
|
||||
{`@${getMemberDisplayName(room, mentionId) ?? getMxIdLocalPart(mentionId)}`}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'span' && 'data-mx-spoiler' in props) {
|
||||
return (
|
||||
<span className={css.Spoiler()} {...props}>
|
||||
{domToReact(children)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'img') {
|
||||
const htmlSrc = mx.mxcUrlToHttp(props.src);
|
||||
if (htmlSrc && props.src.startsWith('mxc://') === false) {
|
||||
return (
|
||||
<a href={htmlSrc} target="_blank" rel="noreferrer noopener">
|
||||
{props.alt && htmlSrc}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
if (htmlSrc && 'data-mx-emoticon' in props) {
|
||||
return (
|
||||
<span className={css.EmoticonBase}>
|
||||
<span className={css.Emoticon()} contentEditable={false}>
|
||||
<img className={css.EmoticonImg} src={htmlSrc} data-mx-emoticon />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (htmlSrc) return <img className={css.Img} {...props} src={htmlSrc} />;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
domNode instanceof DOMText &&
|
||||
!(domNode.parent && 'name' in domNode.parent && domNode.parent.name === 'code')
|
||||
) {
|
||||
return <Linkify options={LINKIFY_OPTS}>{domNode.data}</Linkify>;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
return opts;
|
||||
};
|
|
@ -2,34 +2,49 @@ import { style } from '@vanilla-extract/css';
|
|||
import { recipe } from '@vanilla-extract/recipes';
|
||||
import { color, config, DefaultReset, toRem } from 'folds';
|
||||
|
||||
const MarginBottom = style({
|
||||
export const MarginSpaced = style({
|
||||
marginBottom: config.space.S200,
|
||||
marginTop: config.space.S200,
|
||||
selectors: {
|
||||
'&:first-child': {
|
||||
marginTop: 0,
|
||||
},
|
||||
'&:last-child': {
|
||||
marginBottom: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const Paragraph = style([MarginBottom]);
|
||||
export const Paragraph = style([DefaultReset]);
|
||||
|
||||
export const Heading = style([MarginBottom]);
|
||||
export const Heading = style([
|
||||
DefaultReset,
|
||||
MarginSpaced,
|
||||
{
|
||||
marginTop: config.space.S400,
|
||||
selectors: {
|
||||
'&:first-child': {
|
||||
marginTop: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const BlockQuote = style([
|
||||
DefaultReset,
|
||||
MarginBottom,
|
||||
MarginSpaced,
|
||||
{
|
||||
paddingLeft: config.space.S200,
|
||||
borderLeft: `${config.borderWidth.B700} solid ${color.SurfaceVariant.ContainerLine}`,
|
||||
borderLeft: `${config.borderWidth.B700} solid ${color.Surface.ContainerLine}`,
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
]);
|
||||
|
||||
const BaseCode = style({
|
||||
fontFamily: 'monospace',
|
||||
color: color.Warning.OnContainer,
|
||||
background: color.Warning.Container,
|
||||
border: `${config.borderWidth.B300} solid ${color.Warning.ContainerLine}`,
|
||||
color: color.Secondary.OnContainer,
|
||||
background: color.Secondary.Container,
|
||||
border: `${config.borderWidth.B300} solid ${color.Secondary.ContainerLine}`,
|
||||
borderRadius: config.radii.R300,
|
||||
});
|
||||
|
||||
|
@ -40,29 +55,48 @@ export const Code = style([
|
|||
padding: `0 ${config.space.S100}`,
|
||||
},
|
||||
]);
|
||||
export const Spoiler = style([
|
||||
DefaultReset,
|
||||
{
|
||||
padding: `0 ${config.space.S100}`,
|
||||
backgroundColor: color.SurfaceVariant.ContainerActive,
|
||||
borderRadius: config.radii.R300,
|
||||
},
|
||||
]);
|
||||
|
||||
export const CodeBlock = style([DefaultReset, BaseCode, MarginBottom]);
|
||||
export const Spoiler = recipe({
|
||||
base: [
|
||||
DefaultReset,
|
||||
{
|
||||
padding: `0 ${config.space.S100}`,
|
||||
backgroundColor: color.SurfaceVariant.ContainerActive,
|
||||
borderRadius: config.radii.R300,
|
||||
},
|
||||
],
|
||||
variants: {
|
||||
active: {
|
||||
true: {
|
||||
color: 'transparent',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const CodeBlock = style([DefaultReset, BaseCode, MarginSpaced]);
|
||||
export const CodeBlockInternal = style({
|
||||
padding: `${config.space.S200} ${config.space.S200} 0`,
|
||||
});
|
||||
|
||||
export const List = style([
|
||||
DefaultReset,
|
||||
MarginBottom,
|
||||
MarginSpaced,
|
||||
{
|
||||
padding: `0 ${config.space.S100}`,
|
||||
paddingLeft: config.space.S600,
|
||||
},
|
||||
]);
|
||||
|
||||
export const Img = style([
|
||||
DefaultReset,
|
||||
MarginSpaced,
|
||||
{
|
||||
maxWidth: toRem(296),
|
||||
borderRadius: config.radii.R300,
|
||||
},
|
||||
]);
|
||||
|
||||
export const InlineChromiumBugfix = style({
|
||||
fontSize: 0,
|
||||
lineHeight: 0,
|
||||
|
@ -83,9 +117,9 @@ export const Mention = recipe({
|
|||
variants: {
|
||||
highlight: {
|
||||
true: {
|
||||
backgroundColor: color.Primary.Container,
|
||||
color: color.Primary.OnContainer,
|
||||
boxShadow: `0 0 0 ${config.borderWidth.B300} ${color.Primary.ContainerLine}`,
|
||||
backgroundColor: color.Success.Container,
|
||||
color: color.Success.OnContainer,
|
||||
boxShadow: `0 0 0 ${config.borderWidth.B300} ${color.Success.ContainerLine}`,
|
||||
},
|
||||
},
|
||||
focus: {
|
Loading…
Reference in a new issue