Add 'scrollable' prop to Markdown component for better control over scrolling behavior (#964)

The scrollable prop controls if ScrollbarOverlay is added to the
Markdown component.
This commit is contained in:
Red J Adaya 2024-10-07 04:30:02 +08:00 committed by GitHub
parent f1fe401dbe
commit 28ca193c19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 59 additions and 9 deletions

View File

@ -7,6 +7,7 @@
overflow: hidden; overflow: hidden;
height: 100%; height: 100%;
width: 100%; width: 100%;
.content { .content {
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -17,6 +18,10 @@
font-size: 14px; font-size: 14px;
overflow-wrap: break-word; overflow-wrap: break-word;
&.non-scrollable {
overflow: hidden;
}
.heading { .heading {
&:first-of-type { &:first-of-type {
margin-top: 0 !important; margin-top: 0 !important;

View File

@ -178,9 +178,19 @@ type MarkdownProps = {
className?: string; className?: string;
onClickExecute?: (cmd: string) => void; onClickExecute?: (cmd: string) => void;
resolveOpts?: MarkdownResolveOpts; resolveOpts?: MarkdownResolveOpts;
scrollable?: boolean;
}; };
const Markdown = ({ text, textAtom, showTocAtom, style, className, resolveOpts, onClickExecute }: MarkdownProps) => { const Markdown = ({
text,
textAtom,
showTocAtom,
style,
className,
resolveOpts,
scrollable = true,
onClickExecute,
}: MarkdownProps) => {
const textAtomValue = useAtomValueSafe(textAtom); const textAtomValue = useAtomValueSafe(textAtom);
const tocRef = useRef<TocItem[]>([]); const tocRef = useRef<TocItem[]>([]);
const showToc = useAtomValueSafe(showTocAtom) ?? false; const showToc = useAtomValueSafe(showTocAtom) ?? false;
@ -240,8 +250,8 @@ const Markdown = ({ text, textAtom, showTocAtom, style, className, resolveOpts,
text = textAtomValue ?? text; text = textAtomValue ?? text;
return ( const ScrollableMarkdown = () => {
<div className={clsx("markdown", className)} style={style}> return (
<OverlayScrollbarsComponent <OverlayScrollbarsComponent
ref={contentsOsRef} ref={contentsOsRef}
className="content" className="content"
@ -274,6 +284,45 @@ const Markdown = ({ text, textAtom, showTocAtom, style, className, resolveOpts,
{text} {text}
</ReactMarkdown> </ReactMarkdown>
</OverlayScrollbarsComponent> </OverlayScrollbarsComponent>
);
};
const NonScrollableMarkdown = () => {
return (
<div className="content non-scrollable">
<ReactMarkdown
remarkPlugins={[remarkGfm, [RemarkFlexibleToc, { tocRef: tocRef.current }]]}
rehypePlugins={[
rehypeRaw,
rehypeHighlight,
() =>
rehypeSanitize({
...defaultSchema,
attributes: {
...defaultSchema.attributes,
span: [
...(defaultSchema.attributes?.span || []),
// Allow all class names starting with `hljs-`.
["className", /^hljs-./],
// Alternatively, to allow only certain class names:
// ['className', 'hljs-number', 'hljs-title', 'hljs-variable']
],
},
tagNames: [...(defaultSchema.tagNames || []), "span"],
}),
() => rehypeSlug({ prefix: idPrefix }),
]}
components={markdownComponents}
>
{text}
</ReactMarkdown>
</div>
);
};
return (
<div className={clsx("markdown", className)} style={style}>
{scrollable ? <ScrollableMarkdown /> : <NonScrollableMarkdown />}
{toc && ( {toc && (
<OverlayScrollbarsComponent className="toc" options={{ scrollbars: { autoHide: "leave" } }}> <OverlayScrollbarsComponent className="toc" options={{ scrollbars: { autoHide: "leave" } }}>
<div className="toc-inner"> <div className="toc-inner">

View File

@ -14,7 +14,6 @@ import { atom, Atom, PrimitiveAtom, useAtomValue, useSetAtom, WritableAtom } fro
import type { OverlayScrollbars } from "overlayscrollbars"; import type { OverlayScrollbars } from "overlayscrollbars";
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react"; import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import tinycolor from "tinycolor2";
import "./waveai.less"; import "./waveai.less";
interface ChatMessageType { interface ChatMessageType {
@ -263,11 +262,8 @@ function makeWaveAiViewModel(blockId): WaveAiModel {
const ChatItem = ({ chatItem }: ChatItemProps) => { const ChatItem = ({ chatItem }: ChatItemProps) => {
const { isAssistant, text, isError } = chatItem; const { isAssistant, text, isError } = chatItem;
const senderClassName = isAssistant ? "chat-msg-assistant" : "chat-msg-user";
const msgClassName = `chat-msg ${senderClassName}`;
const cssVar = "--panel-bg-color"; const cssVar = "--panel-bg-color";
const panelBgColor = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim(); const panelBgColor = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim();
const color = tinycolor(panelBgColor);
const renderError = (err: string): React.JSX.Element => <div className="chat-msg-error">{err}</div>; const renderError = (err: string): React.JSX.Element => <div className="chat-msg-error">{err}</div>;
@ -284,7 +280,7 @@ const ChatItem = ({ chatItem }: ChatItemProps) => {
</div> </div>
</div> </div>
<div className="chat-msg chat-msg-assistant"> <div className="chat-msg chat-msg-assistant">
<Markdown text={text} /> <Markdown text={text} scrollable={false} />
</div> </div>
</> </>
) : ( ) : (
@ -299,7 +295,7 @@ const ChatItem = ({ chatItem }: ChatItemProps) => {
return ( return (
<> <>
<div className="chat-msg chat-msg-user"> <div className="chat-msg chat-msg-user">
<Markdown className="msg-text" text={text} /> <Markdown className="msg-text" text={text} scrollable={false} />
</div> </div>
</> </>
); );