mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
relative markdown text (#1489)
This commit is contained in:
parent
51bd45bd2b
commit
f1cd6b933d
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -20,6 +20,9 @@
|
||||
"[less]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
|
@ -1,6 +1,12 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import { getWebServerEndpoint } from "@/util/endpoints";
|
||||
import { isBlank, makeConnRoute } from "@/util/util";
|
||||
import parseSrcSet from "parse-srcset";
|
||||
|
||||
export type MarkdownContentBlockType = {
|
||||
type: string;
|
||||
id: string;
|
||||
@ -147,3 +153,56 @@ export function transformBlocks(content: string): { content: string; blocks: Map
|
||||
blocks: blocks,
|
||||
};
|
||||
}
|
||||
|
||||
export const resolveRemoteFile = async (filepath: string, resolveOpts: MarkdownResolveOpts): Promise<string | null> => {
|
||||
if (!filepath || filepath.startsWith("http://") || filepath.startsWith("https://")) {
|
||||
return filepath;
|
||||
}
|
||||
|
||||
try {
|
||||
const route = makeConnRoute(resolveOpts.connName);
|
||||
const fileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [resolveOpts.baseDir, filepath], {
|
||||
route: route,
|
||||
});
|
||||
|
||||
const usp = new URLSearchParams();
|
||||
usp.set("path", fileInfo.path);
|
||||
if (!isBlank(resolveOpts.connName)) {
|
||||
usp.set("connection", resolveOpts.connName);
|
||||
}
|
||||
|
||||
return getWebServerEndpoint() + "/wave/stream-file?" + usp.toString();
|
||||
} catch (err) {
|
||||
console.warn("Failed to resolve remote file:", filepath, err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const resolveSrcSet = async (srcSet: string, resolveOpts: MarkdownResolveOpts): Promise<string> => {
|
||||
if (!srcSet) return null;
|
||||
|
||||
// Parse the srcset
|
||||
const candidates = parseSrcSet(srcSet);
|
||||
|
||||
// Resolve each URL in the array of candidates
|
||||
const resolvedCandidates = await Promise.all(
|
||||
candidates.map(async (candidate) => {
|
||||
const resolvedUrl = await resolveRemoteFile(candidate.url, resolveOpts);
|
||||
return {
|
||||
...candidate,
|
||||
url: resolvedUrl,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Reconstruct the srcset string
|
||||
return resolvedCandidates
|
||||
.map((candidate) => {
|
||||
let part = candidate.url;
|
||||
if (candidate.w) part += ` ${candidate.w}w`;
|
||||
if (candidate.h) part += ` ${candidate.h}h`;
|
||||
if (candidate.d) part += ` ${candidate.d}x`;
|
||||
return part;
|
||||
})
|
||||
.join(", ");
|
||||
};
|
||||
|
@ -16,21 +16,66 @@
|
||||
overflow: scroll;
|
||||
line-height: 1.5;
|
||||
color: var(--main-text-color);
|
||||
font-family: var(--markdown-font);
|
||||
font-size: 14px;
|
||||
font-family: var(--markdown-font-family);
|
||||
font-size: var(--markdown-font-size);
|
||||
overflow-wrap: break-word;
|
||||
|
||||
&.non-scrollable {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.heading:not(.heading ~ .heading) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.heading {
|
||||
&:first-of-type {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
color: var(--main-text-color);
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 1.143em;
|
||||
margin-bottom: 0.571em;
|
||||
font-weight: semibold;
|
||||
padding-top: 0.429em;
|
||||
|
||||
&.is-1 {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 0.429em;
|
||||
font-size: 2em;
|
||||
}
|
||||
&.is-2 {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 0.429em;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
&.is-3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
&.is-4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
&.is-5 {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
&.is-6 {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
max-width: 100%;
|
||||
box-sizing: content-box;
|
||||
|
||||
&[align="right"] {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
&[align="left"] {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
@ -44,24 +89,24 @@
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
list-style-position: outside;
|
||||
margin-left: 16px;
|
||||
margin-left: 1.143em;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-position: outside;
|
||||
margin-left: 19px;
|
||||
margin-left: 1.357em;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 4px 10px 4px 10px;
|
||||
border-radius: 3px;
|
||||
margin: 0.286em 0.714em;
|
||||
border-radius: 4px;
|
||||
background-color: var(--panel-bg-color);
|
||||
padding: 2px 4px 2px 6px;
|
||||
padding: 0.143em 0.286em 0.143em 0.429em;
|
||||
}
|
||||
|
||||
pre.codeblock {
|
||||
background-color: var(--panel-bg-color);
|
||||
margin: 4px 10px;
|
||||
margin: 0.286em 0.714em;
|
||||
padding: 0.4em 0.7em;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
@ -83,11 +128,11 @@
|
||||
right: 0;
|
||||
border-radius: 4px;
|
||||
backdrop-filter: blur(8px);
|
||||
margin: 2px 2px;
|
||||
padding: 4px 4px;
|
||||
margin: 0.143em;
|
||||
padding: 0.286em;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
gap: 0.286em;
|
||||
}
|
||||
|
||||
&:hover .codeblock-actions {
|
||||
@ -98,6 +143,7 @@
|
||||
code {
|
||||
color: var(--main-text-color);
|
||||
font: var(--fixed-font);
|
||||
font-size: var(--markdown-fixed-font-size);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@ -105,41 +151,13 @@
|
||||
outline: 2px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-weight: semibold;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.heading.is-1 {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 6px;
|
||||
font-size: 2em;
|
||||
}
|
||||
.heading.is-2 {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 6px;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.heading.is-3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
.heading.is-4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
.heading.is-5 {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
.heading.is-6 {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.waveblock {
|
||||
margin: 16px 0;
|
||||
margin: 1.143em 0;
|
||||
|
||||
.wave-block-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
padding: 0.857em;
|
||||
background-color: var(--highlight-bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
@ -150,15 +168,15 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 2.857em;
|
||||
height: 2.857em;
|
||||
background-color: black;
|
||||
border-radius: 8px;
|
||||
margin-right: 12px;
|
||||
margin-right: 0.857em;
|
||||
}
|
||||
|
||||
.wave-block-icon i {
|
||||
font-size: 18px;
|
||||
font-size: 1.125em;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
@ -168,19 +186,18 @@
|
||||
}
|
||||
|
||||
.wave-block-filename {
|
||||
font-size: 14px;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.wave-block-size {
|
||||
font-size: 12px;
|
||||
font-size: 0.857em;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The TOC view should scroll independently of the contents view.
|
||||
.toc {
|
||||
max-width: 40%;
|
||||
height: 100%;
|
||||
@ -192,21 +209,20 @@
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
gap: 0.357em;
|
||||
text-wrap: wrap;
|
||||
|
||||
h4 {
|
||||
padding-left: 5px;
|
||||
padding-left: 0.357em;
|
||||
}
|
||||
|
||||
.toc-item {
|
||||
cursor: pointer;
|
||||
--indent-factor: 1;
|
||||
|
||||
// The 5px offset in the padding will ensure that when the text in the item wraps, it indents slightly.
|
||||
// The offset in the padding will ensure that when the text in the item wraps, it indents slightly.
|
||||
// The indent factor is set in the React code and denotes the depth of the item in the TOC tree.
|
||||
padding-left: calc((var(--indent-factor) - 1) * 10px + 5px);
|
||||
text-indent: -5px;
|
||||
padding-left: calc((var(--indent-factor) - 1) * 0.714em + 0.357em);
|
||||
text-indent: -0.357em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,13 @@
|
||||
|
||||
import { CopyButton } from "@/app/element/copybutton";
|
||||
import { createContentBlockPlugin } from "@/app/element/markdown-contentblock-plugin";
|
||||
import { MarkdownContentBlockType, transformBlocks } from "@/app/element/markdown-util";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import { getWebServerEndpoint } from "@/util/endpoints";
|
||||
import { isBlank, makeConnRoute, useAtomValueSafe } from "@/util/util";
|
||||
import {
|
||||
MarkdownContentBlockType,
|
||||
resolveRemoteFile,
|
||||
resolveSrcSet,
|
||||
transformBlocks,
|
||||
} from "@/app/element/markdown-util";
|
||||
import { useAtomValueSafe } from "@/util/util";
|
||||
import { clsx } from "clsx";
|
||||
import { Atom } from "jotai";
|
||||
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||
@ -108,8 +110,34 @@ const CodeBlock = ({ children, onClickExecute }: CodeBlockProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const MarkdownSource = (props: React.HTMLAttributes<HTMLSourceElement>) => {
|
||||
return null;
|
||||
const MarkdownSource = ({
|
||||
props,
|
||||
resolveOpts,
|
||||
}: {
|
||||
props: React.HTMLAttributes<HTMLSourceElement> & {
|
||||
srcSet?: string;
|
||||
media?: string;
|
||||
};
|
||||
resolveOpts: MarkdownResolveOpts;
|
||||
}) => {
|
||||
const [resolvedSrcSet, setResolvedSrcSet] = useState<string>(props.srcSet);
|
||||
const [resolving, setResolving] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const resolvePath = async () => {
|
||||
const resolved = await resolveSrcSet(props.srcSet, resolveOpts);
|
||||
setResolvedSrcSet(resolved);
|
||||
setResolving(false);
|
||||
};
|
||||
|
||||
resolvePath();
|
||||
}, [props.srcSet]);
|
||||
|
||||
if (resolving) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <source srcSet={resolvedSrcSet} media={props.media} />;
|
||||
};
|
||||
|
||||
interface WaveBlockProps {
|
||||
@ -148,16 +176,11 @@ const MarkdownImg = ({
|
||||
resolveOpts: MarkdownResolveOpts;
|
||||
}) => {
|
||||
const [resolvedSrc, setResolvedSrc] = useState<string>(props.src);
|
||||
const [resolvedSrcSet, setResolvedSrcSet] = useState<string>(props.srcSet);
|
||||
const [resolvedStr, setResolvedStr] = useState<string>(null);
|
||||
const [resolving, setResolving] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.src.startsWith("http://") || props.src.startsWith("https://")) {
|
||||
setResolving(false);
|
||||
setResolvedSrc(props.src);
|
||||
setResolvedStr(null);
|
||||
return;
|
||||
}
|
||||
if (props.src.startsWith("data:image/")) {
|
||||
setResolving(false);
|
||||
setResolvedSrc(props.src);
|
||||
@ -170,23 +193,20 @@ const MarkdownImg = ({
|
||||
setResolvedStr(`[img:${props.src}]`);
|
||||
return;
|
||||
}
|
||||
|
||||
const resolveFn = async () => {
|
||||
const route = makeConnRoute(resolveOpts.connName);
|
||||
const fileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [resolveOpts.baseDir, props.src], {
|
||||
route: route,
|
||||
});
|
||||
const usp = new URLSearchParams();
|
||||
usp.set("path", fileInfo.path);
|
||||
if (!isBlank(resolveOpts.connName)) {
|
||||
usp.set("connection", resolveOpts.connName);
|
||||
}
|
||||
const streamingUrl = getWebServerEndpoint() + "/wave/stream-file?" + usp.toString();
|
||||
setResolvedSrc(streamingUrl);
|
||||
const [resolvedSrc, resolvedSrcSet] = await Promise.all([
|
||||
resolveRemoteFile(props.src, resolveOpts),
|
||||
resolveSrcSet(props.srcSet, resolveOpts),
|
||||
]);
|
||||
|
||||
setResolvedSrc(resolvedSrc);
|
||||
setResolvedSrcSet(resolvedSrcSet);
|
||||
setResolvedStr(null);
|
||||
setResolving(false);
|
||||
};
|
||||
resolveFn();
|
||||
}, [props.src]);
|
||||
}, [props.src, props.srcSet]);
|
||||
|
||||
if (resolving) {
|
||||
return null;
|
||||
@ -195,7 +215,7 @@ const MarkdownImg = ({
|
||||
return <span>{resolvedStr}</span>;
|
||||
}
|
||||
if (resolvedSrc != null) {
|
||||
return <img {...props} src={resolvedSrc} />;
|
||||
return <img {...props} src={resolvedSrc} srcSet={resolvedSrcSet} />;
|
||||
}
|
||||
return <span>[img]</span>;
|
||||
};
|
||||
@ -210,6 +230,8 @@ type MarkdownProps = {
|
||||
resolveOpts?: MarkdownResolveOpts;
|
||||
scrollable?: boolean;
|
||||
rehype?: boolean;
|
||||
fontSizeOverride?: number;
|
||||
fixedFontSizeOverride?: number;
|
||||
};
|
||||
|
||||
const Markdown = ({
|
||||
@ -219,6 +241,8 @@ const Markdown = ({
|
||||
style,
|
||||
className,
|
||||
resolveOpts,
|
||||
fontSizeOverride,
|
||||
fixedFontSizeOverride,
|
||||
scrollable = true,
|
||||
rehype = true,
|
||||
onClickExecute,
|
||||
@ -262,7 +286,9 @@ const Markdown = ({
|
||||
h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => <Heading props={props} hnum={5} />,
|
||||
h6: (props: React.HTMLAttributes<HTMLHeadingElement>) => <Heading props={props} hnum={6} />,
|
||||
img: (props: React.HTMLAttributes<HTMLImageElement>) => <MarkdownImg props={props} resolveOpts={resolveOpts} />,
|
||||
source: (props: React.HTMLAttributes<HTMLSourceElement>) => <MarkdownSource {...props} />,
|
||||
source: (props: React.HTMLAttributes<HTMLSourceElement>) => (
|
||||
<MarkdownSource props={props} resolveOpts={resolveOpts} />
|
||||
),
|
||||
code: Code,
|
||||
pre: (props: React.HTMLAttributes<HTMLPreElement>) => (
|
||||
<CodeBlock children={props.children} onClickExecute={onClickExecute} />
|
||||
@ -301,12 +327,15 @@ const Markdown = ({
|
||||
...(defaultSchema.attributes?.span || []),
|
||||
// Allow all class names starting with `hljs-`.
|
||||
["className", /^hljs-./],
|
||||
["srcset"],
|
||||
["media"],
|
||||
["type"],
|
||||
// Alternatively, to allow only certain class names:
|
||||
// ['className', 'hljs-number', 'hljs-title', 'hljs-variable']
|
||||
],
|
||||
waveblock: [["blockkey"]],
|
||||
},
|
||||
tagNames: [...(defaultSchema.tagNames || []), "span", "waveblock"],
|
||||
tagNames: [...(defaultSchema.tagNames || []), "span", "waveblock", "picture", "source"],
|
||||
}),
|
||||
() => rehypeSlug({ prefix: idPrefix }),
|
||||
];
|
||||
@ -349,8 +378,15 @@ const Markdown = ({
|
||||
);
|
||||
};
|
||||
|
||||
const mergedStyle = { ...style };
|
||||
if (fontSizeOverride != null) {
|
||||
mergedStyle["--markdown-font-size"] = `${fontSizeOverride}px`;
|
||||
}
|
||||
if (fixedFontSizeOverride != null) {
|
||||
mergedStyle["--markdown-fixed-font-size"] = `${fixedFontSizeOverride}px`;
|
||||
}
|
||||
return (
|
||||
<div className={clsx("markdown", className)} style={style}>
|
||||
<div className={clsx("markdown", className)} style={mergedStyle}>
|
||||
{scrollable ? <ScrollableMarkdown /> : <NonScrollableMarkdown />}
|
||||
{toc && (
|
||||
<OverlayScrollbarsComponent className="toc" options={{ scrollbars: { autoHide: "leave" } }}>
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Used for syntax highlighting in markdown
|
||||
|
||||
:root {
|
||||
--main-text-color: #f7f7f7;
|
||||
--title-font-size: 18px;
|
||||
@ -16,8 +14,10 @@
|
||||
--accent-color: rgb(88, 193, 66);
|
||||
--panel-bg-color: rgba(31, 33, 31, 0.5);
|
||||
--highlight-bg-color: rgba(255, 255, 255, 0.2);
|
||||
--markdown-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif,
|
||||
--markdown-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji";
|
||||
--markdown-font-size: 14px;
|
||||
--markdown-fixed-font-size: 12px;
|
||||
--error-color: rgb(229, 77, 46);
|
||||
--warning-color: rgb(224, 185, 86);
|
||||
--success-color: rgb(78, 154, 6);
|
||||
|
@ -783,6 +783,8 @@ function makePreviewModel(blockId: string, nodeModel: BlockNodeModel): PreviewMo
|
||||
function MarkdownPreview({ model }: SpecializedViewProps) {
|
||||
const connName = useAtomValue(model.connection);
|
||||
const fileInfo = useAtomValue(model.statFile);
|
||||
const fontSizeOverride = useAtomValue(getOverrideConfigAtom(model.blockId, "markdown:fontsize"));
|
||||
const fixedFontSizeOverride = useAtomValue(getOverrideConfigAtom(model.blockId, "markdown:fixedfontsize"));
|
||||
const resolveOpts: MarkdownResolveOpts = useMemo<MarkdownResolveOpts>(() => {
|
||||
return {
|
||||
connName: connName,
|
||||
@ -791,7 +793,13 @@ function MarkdownPreview({ model }: SpecializedViewProps) {
|
||||
}, [connName, fileInfo.dir]);
|
||||
return (
|
||||
<div className="view-preview view-preview-markdown">
|
||||
<Markdown textAtom={model.fileContent} showTocAtom={model.markdownShowToc} resolveOpts={resolveOpts} />
|
||||
<Markdown
|
||||
textAtom={model.fileContent}
|
||||
showTocAtom={model.markdownShowToc}
|
||||
resolveOpts={resolveOpts}
|
||||
fontSizeOverride={fontSizeOverride}
|
||||
fixedFontSizeOverride={fixedFontSizeOverride}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -131,9 +131,6 @@
|
||||
outline: none;
|
||||
overflow: auto;
|
||||
overflow-wrap: anywhere;
|
||||
font-family: var(--termfontfamily);
|
||||
font-weight: normal;
|
||||
line-height: var(--termlineheight);
|
||||
height: 21px;
|
||||
}
|
||||
}
|
||||
|
@ -569,7 +569,7 @@ const ChatWindow = memo(
|
||||
|
||||
interface ChatInputProps {
|
||||
value: string;
|
||||
termFontSize: number;
|
||||
baseFontSize: number;
|
||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||
onMouseDown: (e: React.MouseEvent<HTMLTextAreaElement>) => void;
|
||||
@ -577,7 +577,7 @@ interface ChatInputProps {
|
||||
}
|
||||
|
||||
const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
||||
({ value, onChange, onKeyDown, onMouseDown, termFontSize, model }, ref) => {
|
||||
({ value, onChange, onKeyDown, onMouseDown, baseFontSize, model }, ref) => {
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useImperativeHandle(ref, () => textAreaRef.current as HTMLTextAreaElement);
|
||||
@ -594,7 +594,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
||||
|
||||
// Adjust the height of the textarea to fit the text
|
||||
const textAreaMaxLines = 5;
|
||||
const textAreaLineHeight = termFontSize * 1.5;
|
||||
const textAreaLineHeight = baseFontSize * 1.5;
|
||||
const textAreaMinHeight = textAreaLineHeight;
|
||||
const textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines;
|
||||
|
||||
@ -608,7 +608,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
||||
const newHeight = Math.min(Math.max(scrollHeight, textAreaMinHeight), textAreaMaxHeight);
|
||||
textAreaRef.current.style.height = newHeight + "px";
|
||||
},
|
||||
[termFontSize]
|
||||
[baseFontSize]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -624,7 +624,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
||||
onMouseDown={onMouseDown} // When the user clicks on the textarea
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
style={{ fontSize: termFontSize }}
|
||||
style={{ fontSize: baseFontSize }}
|
||||
placeholder="Ask anything..."
|
||||
value={value}
|
||||
></textarea>
|
||||
@ -642,7 +642,7 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
||||
const [value, setValue] = useState("");
|
||||
const [selectedBlockIdx, setSelectedBlockIdx] = useState<number | null>(null);
|
||||
|
||||
const termFontSize: number = 14;
|
||||
const baseFontSize: number = 14;
|
||||
const msgWidths = {};
|
||||
const locked = useAtomValue(model.locked);
|
||||
|
||||
@ -815,7 +815,7 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
||||
onChange={handleTextAreaChange}
|
||||
onKeyDown={handleTextAreaKeyDown}
|
||||
onMouseDown={handleTextAreaMouseDown}
|
||||
termFontSize={termFontSize}
|
||||
baseFontSize={baseFontSize}
|
||||
/>
|
||||
</div>
|
||||
<Button className={buttonClass} onClick={handleButtonPress}>
|
||||
|
4
frontend/types/gotypes.d.ts
vendored
4
frontend/types/gotypes.d.ts
vendored
@ -490,6 +490,8 @@ declare global {
|
||||
"term:vdomblockid"?: string;
|
||||
"term:vdomtoolbarblockid"?: string;
|
||||
"web:zoom"?: number;
|
||||
"markdown:fontsize"?: number;
|
||||
"markdown:fixedfontsize"?: number;
|
||||
"vdom:*"?: boolean;
|
||||
"vdom:initialized"?: boolean;
|
||||
"vdom:correlationid"?: string;
|
||||
@ -638,6 +640,8 @@ declare global {
|
||||
"autoupdate:intervalms"?: number;
|
||||
"autoupdate:installonquit"?: boolean;
|
||||
"autoupdate:channel"?: string;
|
||||
"markdown:fontsize"?: number;
|
||||
"markdown:fixedfontsize"?: number;
|
||||
"preview:showhiddenfiles"?: boolean;
|
||||
"tab:preset"?: string;
|
||||
"widget:*"?: boolean;
|
||||
|
@ -116,6 +116,7 @@
|
||||
"overlayscrollbars": "^2.10.1",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"papaparse": "^5.4.1",
|
||||
"parse-srcset": "^1.0.2",
|
||||
"pngjs": "^7.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
|
@ -96,6 +96,9 @@ const (
|
||||
|
||||
MetaKey_WebZoom = "web:zoom"
|
||||
|
||||
MetaKey_MarkdownFontSize = "markdown:fontsize"
|
||||
MetaKey_MarkdownFixedFontSize = "markdown:fixedfontsize"
|
||||
|
||||
MetaKey_VDomClear = "vdom:*"
|
||||
MetaKey_VDomInitialized = "vdom:initialized"
|
||||
MetaKey_VDomCorrelationId = "vdom:correlationid"
|
||||
|
@ -97,6 +97,9 @@ type MetaTSType struct {
|
||||
|
||||
WebZoom float64 `json:"web:zoom,omitempty"`
|
||||
|
||||
MarkdownFontSize float64 `json:"markdown:fontsize,omitempty"`
|
||||
MarkdownFixedFontSize float64 `json:"markdown:fixedfontsize,omitempty"`
|
||||
|
||||
VDomClear bool `json:"vdom:*,omitempty"`
|
||||
VDomInitialized bool `json:"vdom:initialized,omitempty"`
|
||||
VDomCorrelationId string `json:"vdom:correlationid,omitempty"`
|
||||
|
@ -46,6 +46,9 @@ const (
|
||||
ConfigKey_AutoUpdateInstallOnQuit = "autoupdate:installonquit"
|
||||
ConfigKey_AutoUpdateChannel = "autoupdate:channel"
|
||||
|
||||
ConfigKey_MarkdownFontSize = "markdown:fontsize"
|
||||
ConfigKey_MarkdownFixedFontSize = "markdown:fixedfontsize"
|
||||
|
||||
ConfigKey_PreviewShowHiddenFiles = "preview:showhiddenfiles"
|
||||
|
||||
ConfigKey_TabPreset = "tab:preset"
|
||||
|
@ -73,6 +73,9 @@ type SettingsType struct {
|
||||
AutoUpdateInstallOnQuit bool `json:"autoupdate:installonquit,omitempty"`
|
||||
AutoUpdateChannel string `json:"autoupdate:channel,omitempty"`
|
||||
|
||||
MarkdownFontSize float64 `json:"markdown:fontsize,omitempty"`
|
||||
MarkdownFixedFontSize float64 `json:"markdown:fixedfontsize,omitempty"`
|
||||
|
||||
PreviewShowHiddenFiles *bool `json:"preview:showhiddenfiles,omitempty"`
|
||||
|
||||
TabPreset string `json:"tab:preset,omitempty"`
|
||||
|
@ -16290,6 +16290,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parse-srcset@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "parse-srcset@npm:1.0.2"
|
||||
checksum: 10c0/2f268e3d110d4c53d06ed2a8e8ee61a7da0cee13bf150819a6da066a8ca9b8d15b5600d6e6cae8be940e2edc50ee7c1e1052934d6ec858324065ecef848f0497
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parse5-htmlparser2-tree-adapter@npm:^7.0.0":
|
||||
version: 7.1.0
|
||||
resolution: "parse5-htmlparser2-tree-adapter@npm:7.1.0"
|
||||
@ -22003,6 +22010,7 @@ __metadata:
|
||||
overlayscrollbars: "npm:^2.10.1"
|
||||
overlayscrollbars-react: "npm:^0.5.6"
|
||||
papaparse: "npm:^5.4.1"
|
||||
parse-srcset: "npm:^1.0.2"
|
||||
pngjs: "npm:^7.0.0"
|
||||
prettier: "npm:^3.4.2"
|
||||
prettier-plugin-jsdoc: "npm:^1.3.0"
|
||||
|
Loading…
Reference in New Issue
Block a user