forked from XiaoMo/ChatGPT-Next-Web
feat: close #680 lazy rendering markdown
This commit is contained in:
parent
d790b0b372
commit
8363cdd9fa
@ -1,5 +1,29 @@
|
|||||||
@import "../styles/animation.scss";
|
@import "../styles/animation.scss";
|
||||||
|
|
||||||
|
.chat-input-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.chat-input-action {
|
||||||
|
display: inline-flex;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: var(--white);
|
||||||
|
color: var(--black);
|
||||||
|
border: var(--border-in-light);
|
||||||
|
padding: 4px 10px;
|
||||||
|
animation: slide-in ease 0.3s;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.prompt-toast {
|
.prompt-toast {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -50px;
|
bottom: -50px;
|
||||||
|
@ -14,6 +14,11 @@ import DeleteIcon from "../icons/delete.svg";
|
|||||||
import MaxIcon from "../icons/max.svg";
|
import MaxIcon from "../icons/max.svg";
|
||||||
import MinIcon from "../icons/min.svg";
|
import MinIcon from "../icons/min.svg";
|
||||||
|
|
||||||
|
import LightIcon from "../icons/light.svg";
|
||||||
|
import DarkIcon from "../icons/dark.svg";
|
||||||
|
import AutoIcon from "../icons/auto.svg";
|
||||||
|
import BottomIcon from "../icons/bottom.svg";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Message,
|
Message,
|
||||||
SubmitKey,
|
SubmitKey,
|
||||||
@ -22,6 +27,7 @@ import {
|
|||||||
ROLES,
|
ROLES,
|
||||||
createMessage,
|
createMessage,
|
||||||
useAccessStore,
|
useAccessStore,
|
||||||
|
Theme,
|
||||||
} from "../store";
|
} from "../store";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -31,6 +37,7 @@ import {
|
|||||||
isMobileScreen,
|
isMobileScreen,
|
||||||
selectOrCopy,
|
selectOrCopy,
|
||||||
autoGrowTextArea,
|
autoGrowTextArea,
|
||||||
|
getCSSVar,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
@ -60,7 +67,11 @@ export function Avatar(props: { role: Message["role"] }) {
|
|||||||
const config = useChatStore((state) => state.config);
|
const config = useChatStore((state) => state.config);
|
||||||
|
|
||||||
if (props.role !== "user") {
|
if (props.role !== "user") {
|
||||||
return <BotIcon className={styles["user-avtar"]} />;
|
return (
|
||||||
|
<div className="no-dark">
|
||||||
|
<BotIcon className={styles["user-avtar"]} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -316,22 +327,78 @@ function useScrollToBottom() {
|
|||||||
// for auto-scroll
|
// for auto-scroll
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const [autoScroll, setAutoScroll] = useState(true);
|
const [autoScroll, setAutoScroll] = useState(true);
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
const dom = scrollRef.current;
|
||||||
|
if (dom) {
|
||||||
|
setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// auto scroll
|
// auto scroll
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const dom = scrollRef.current;
|
autoScroll && scrollToBottom();
|
||||||
if (dom && autoScroll) {
|
|
||||||
setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scrollRef,
|
scrollRef,
|
||||||
autoScroll,
|
autoScroll,
|
||||||
setAutoScroll,
|
setAutoScroll,
|
||||||
|
scrollToBottom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ChatActions(props: {
|
||||||
|
showPromptModal: () => void;
|
||||||
|
scrollToBottom: () => void;
|
||||||
|
hitBottom: boolean;
|
||||||
|
}) {
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
|
const theme = chatStore.config.theme;
|
||||||
|
|
||||||
|
function nextTheme() {
|
||||||
|
const themes = [Theme.Auto, Theme.Light, Theme.Dark];
|
||||||
|
const themeIndex = themes.indexOf(theme);
|
||||||
|
const nextIndex = (themeIndex + 1) % themes.length;
|
||||||
|
const nextTheme = themes[nextIndex];
|
||||||
|
chatStore.updateConfig((config) => (config.theme = nextTheme));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={chatStyle["chat-input-actions"]}>
|
||||||
|
{!props.hitBottom && (
|
||||||
|
<div
|
||||||
|
className={`${chatStyle["chat-input-action"]} clickable`}
|
||||||
|
onClick={props.scrollToBottom}
|
||||||
|
>
|
||||||
|
<BottomIcon />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{props.hitBottom && (
|
||||||
|
<div
|
||||||
|
className={`${chatStyle["chat-input-action"]} clickable`}
|
||||||
|
onClick={props.showPromptModal}
|
||||||
|
>
|
||||||
|
<BrainIcon />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`${chatStyle["chat-input-action"]} clickable`}
|
||||||
|
onClick={nextTheme}
|
||||||
|
>
|
||||||
|
{theme === Theme.Auto ? (
|
||||||
|
<AutoIcon />
|
||||||
|
) : theme === Theme.Light ? (
|
||||||
|
<LightIcon />
|
||||||
|
) : theme === Theme.Dark ? (
|
||||||
|
<DarkIcon />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function Chat(props: {
|
export function Chat(props: {
|
||||||
showSideBar?: () => void;
|
showSideBar?: () => void;
|
||||||
sideBarShowing?: boolean;
|
sideBarShowing?: boolean;
|
||||||
@ -350,7 +417,7 @@ export function Chat(props: {
|
|||||||
const [beforeInput, setBeforeInput] = useState("");
|
const [beforeInput, setBeforeInput] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { submitKey, shouldSubmit } = useSubmitHandler();
|
const { submitKey, shouldSubmit } = useSubmitHandler();
|
||||||
const { scrollRef, setAutoScroll } = useScrollToBottom();
|
const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom();
|
||||||
const [hitBottom, setHitBottom] = useState(false);
|
const [hitBottom, setHitBottom] = useState(false);
|
||||||
|
|
||||||
const onChatBodyScroll = (e: HTMLElement) => {
|
const onChatBodyScroll = (e: HTMLElement) => {
|
||||||
@ -683,6 +750,8 @@ export function Chat(props: {
|
|||||||
if (!isMobileScreen()) return;
|
if (!isMobileScreen()) return;
|
||||||
setUserInput(message.content);
|
setUserInput(message.content);
|
||||||
}}
|
}}
|
||||||
|
fontSize={fontSize}
|
||||||
|
parentRef={scrollRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!isUser && !message.preview && (
|
{!isUser && !message.preview && (
|
||||||
@ -700,6 +769,12 @@ export function Chat(props: {
|
|||||||
|
|
||||||
<div className={styles["chat-input-panel"]}>
|
<div className={styles["chat-input-panel"]}>
|
||||||
<PromptHints prompts={promptHints} onPromptSelect={onPromptSelect} />
|
<PromptHints prompts={promptHints} onPromptSelect={onPromptSelect} />
|
||||||
|
|
||||||
|
<ChatActions
|
||||||
|
showPromptModal={() => setShowPromptModal(true)}
|
||||||
|
scrollToBottom={scrollToBottom}
|
||||||
|
hitBottom={hitBottom}
|
||||||
|
/>
|
||||||
<div className={styles["chat-input-panel-inner"]}>
|
<div className={styles["chat-input-panel-inner"]}>
|
||||||
<textarea
|
<textarea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
max-height: var(--full-height);
|
max-height: var(--full-height);
|
||||||
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +231,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
padding-bottom: 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,11 +356,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-input-panel {
|
.chat-input-panel {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
padding-top: 5px;
|
padding-top: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
border-top: var(--border-in-light);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin single-line {
|
@mixin single-line {
|
||||||
|
@ -17,7 +17,7 @@ import LoadingIcon from "../icons/three-dots.svg";
|
|||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
|
|
||||||
import { useChatStore } from "../store";
|
import { useChatStore } from "../store";
|
||||||
import { isMobileScreen } from "../utils";
|
import { getCSSVar, isMobileScreen } from "../utils";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { Chat } from "./chat";
|
import { Chat } from "./chat";
|
||||||
|
|
||||||
@ -66,9 +66,7 @@ function useSwitchTheme() {
|
|||||||
metaDescriptionDark?.setAttribute("content", "#151515");
|
metaDescriptionDark?.setAttribute("content", "#151515");
|
||||||
metaDescriptionLight?.setAttribute("content", "#fafafa");
|
metaDescriptionLight?.setAttribute("content", "#fafafa");
|
||||||
} else {
|
} else {
|
||||||
const themeColor = getComputedStyle(document.body)
|
const themeColor = getCSSVar("--themeColor");
|
||||||
.getPropertyValue("--theme-color")
|
|
||||||
.trim();
|
|
||||||
metaDescriptionDark?.setAttribute("content", themeColor);
|
metaDescriptionDark?.setAttribute("content", themeColor);
|
||||||
metaDescriptionLight?.setAttribute("content", themeColor);
|
metaDescriptionLight?.setAttribute("content", themeColor);
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,8 @@ const useLazyLoad = (ref: RefObject<Element>): boolean => {
|
|||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
};
|
};
|
||||||
}, [ref]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
return isIntersecting;
|
return isIntersecting;
|
||||||
};
|
};
|
||||||
@ -57,18 +58,49 @@ export function Markdown(
|
|||||||
content: string;
|
content: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
|
parentRef: RefObject<HTMLDivElement>;
|
||||||
} & React.DOMAttributes<HTMLDivElement>,
|
} & React.DOMAttributes<HTMLDivElement>,
|
||||||
) {
|
) {
|
||||||
const mdRef = useRef(null);
|
const mdRef = useRef<HTMLDivElement>(null);
|
||||||
const shouldRender = useLazyLoad(mdRef);
|
|
||||||
const shouldLoading = props.loading || !shouldRender;
|
const parent = props.parentRef.current;
|
||||||
|
const md = mdRef.current;
|
||||||
|
const rendered = useRef(false);
|
||||||
|
const [counter, setCounter] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// to triggr rerender
|
||||||
|
setCounter(counter + 1);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [props.loading]);
|
||||||
|
|
||||||
|
const inView =
|
||||||
|
rendered.current ||
|
||||||
|
(() => {
|
||||||
|
if (parent && md) {
|
||||||
|
const parentBounds = parent.getBoundingClientRect();
|
||||||
|
const mdBounds = md.getBoundingClientRect();
|
||||||
|
const isInRange = (x: number) =>
|
||||||
|
x <= parentBounds.bottom && x >= parentBounds.top;
|
||||||
|
const inView = isInRange(mdBounds.top) || isInRange(mdBounds.bottom);
|
||||||
|
|
||||||
|
if (inView) {
|
||||||
|
rendered.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inView;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const shouldLoading = props.loading || !inView;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="markdown-body"
|
className="markdown-body"
|
||||||
style={{ fontSize: `${props.fontSize ?? 14}px` }}
|
style={{ fontSize: `${props.fontSize ?? 14}px` }}
|
||||||
{...props}
|
|
||||||
ref={mdRef}
|
ref={mdRef}
|
||||||
|
onContextMenu={props.onContextMenu}
|
||||||
|
onDoubleClickCapture={props.onDoubleClickCapture}
|
||||||
>
|
>
|
||||||
{shouldLoading ? (
|
{shouldLoading ? (
|
||||||
<LoadingIcon />
|
<LoadingIcon />
|
||||||
|
1
app/icons/auto.svg
Normal file
1
app/icons/auto.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="分组 1" style="stroke:#333333; stroke-width:1; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(5.666666666666666 5.333333333333333) rotate(0 2.333750009536743 2.6666666666666665)" d="M0 5.33667L0.73 3.66667 M4.6675 5.33667L3.9375 3.66667 M0.729167 3.67L2.32917 0L3.93917 3.67 M0.729167 3.66667L3.93917 3.66667 " /><path id="路径 5" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.3333333333333333 1.3333333333333333) rotate(0 6.533316666666666 2.6666666666666665)" d="M13.07,5.33C12.45,2.29 9.76,0 6.53,0C3.31,0 0.62,2.29 0,5.33L2,4.67 " /><path id="路径 6" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.3333333333333333 9.333333333333332) rotate(0 6.533316666666666 2.6666666666666665)" d="M0,0C0.62,3.04 3.31,5.33 6.53,5.33C9.76,5.33 12.45,3.04 13.07,0L11.33,0.67 " /></g></g></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
app/icons/bottom.svg
Normal file
1
app/icons/bottom.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(8.002766666666666 2) rotate(0 0 4.649916666666667)" d="M0,9.3L0,0 " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4 7.333333333333333) rotate(0 4 2)" d="M8,0L4,4L0,0 " /><path id="路径 3" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4 14) rotate(0 4 0)" d="M8,0L0,0 " /></g></g></svg>
|
After Width: | Height: | Size: 958 B |
1
app/icons/dark.svg
Normal file
1
app/icons/dark.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.3333333333333333 1.333333333333485) rotate(0 6.666666666666666 6.666666666666666)" d="M6.67,0L4.91,1.76L1.76,1.76L1.76,4.91L0,6.67L1.76,8.42L1.76,11.58L4.91,11.58L6.67,13.33L8.42,11.58L11.58,11.58L11.58,8.42L13.33,6.67L11.58,4.91L11.58,1.76L8.42,1.76L6.67,0Z " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(5.666666666666666 5.44771525016904) rotate(0 2.4732087011352872 2.442809041582063)" d="M4,0.55C2.17,-0.78 0,0.55 0,1.89C1.67,1.89 3.33,2.22 3.33,4.89C4.67,4.89 5.83,1.89 4,0.55Z " /></g></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
app/icons/light.svg
Normal file
1
app/icons/light.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4.340166666666667 4.21550000000002) rotate(0 3.6666666666666665 3.666666666666666)" d="M0,3.67C0,5.69 1.64,7.33 3.67,7.33C5.69,7.33 7.33,5.69 7.33,3.67C7.33,1.64 5.69,0 3.67,0C1.64,0 0,1.64 0,3.67Z " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(12.166666666666666 12.1719333333333) rotate(0 0.4100499999999994 0.41240499999999997)" d="M0.82,0.82L0,0 " /><path id="路径 3" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(3.0068366666666666 3.0654333333332033) rotate(0 0.3411483333333332 0.34309999999999974)" d="M0.68,0.69L0,0 " /><path id="路径 4" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(8 1.2155666666667457) rotate(0 0 0.5)" d="M0,1L0,0 " /><path id="路径 5" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(13.333266666666667 8.21550000000002) rotate(0 0.6666666666666666 0)" d="M1.33,0L0,0 " /><path id="路径 6" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(12.5108 3.065499999999929) rotate(0 0.41123333333333295 0.41123333333333295)" d="M0,0.82L0.82,0 " /><path id="路径 7" fill-rule="evenodd" style="fill:#333333" transform="translate(5.673499999999999 5.5488333333332776) rotate(0 1.1666666666666665 2.333333333333333)" opacity="1" d="M2.33,0C1.04,0 0,1.04 0,2.33C0,3.62 1.04,4.67 2.33,4.67L2.33,0Z " /><path id="路径 8" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.3333966666666666 7.8821666666667625) rotate(0 0.6666666666666666 0)" d="M0,0L1.33,0 " /><path id="路径 9" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(3.348133333333333 12.3125) rotate(0 0.3421333333333335 0.3421266666666665)" d="M0,0.68L0.68,0 " /><path id="路径 10" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(8 13.548763333333454) rotate(0 0 0.6666666666666666)" d="M0,1.33L0,0 " /></g></g></svg>
|
After Width: | Height: | Size: 2.7 KiB |
@ -1,4 +1,6 @@
|
|||||||
@mixin light {
|
@mixin light {
|
||||||
|
--theme: light;
|
||||||
|
|
||||||
/* color */
|
/* color */
|
||||||
--white: white;
|
--white: white;
|
||||||
--black: rgb(48, 48, 48);
|
--black: rgb(48, 48, 48);
|
||||||
@ -18,6 +20,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mixin dark {
|
@mixin dark {
|
||||||
|
--theme: dark;
|
||||||
|
|
||||||
/* color */
|
/* color */
|
||||||
--white: rgb(30, 30, 30);
|
--white: rgb(30, 30, 30);
|
||||||
--black: rgb(187, 187, 187);
|
--black: rgb(187, 187, 187);
|
||||||
@ -31,6 +35,10 @@
|
|||||||
--border-in-light: 1px solid rgba(255, 255, 255, 0.192);
|
--border-in-light: 1px solid rgba(255, 255, 255, 0.192);
|
||||||
|
|
||||||
--theme-color: var(--gray);
|
--theme-color: var(--gray);
|
||||||
|
|
||||||
|
div:not(.no-dark) > svg {
|
||||||
|
filter: invert(0.5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.light {
|
.light {
|
||||||
@ -282,10 +290,6 @@ pre {
|
|||||||
.clickable {
|
.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
div:not(.no-dark) > svg {
|
|
||||||
filter: invert(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
filter: brightness(0.9);
|
filter: brightness(0.9);
|
||||||
}
|
}
|
||||||
|
@ -120,3 +120,7 @@ export function autoGrowTextArea(dom: HTMLTextAreaElement) {
|
|||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCSSVar(varName: string) {
|
||||||
|
return getComputedStyle(document.body).getPropertyValue(varName).trim();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user