mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
fix markdown spacing draft
This commit is contained in:
parent
1b77fc7091
commit
ead1d9229f
4 changed files with 216 additions and 60 deletions
|
|
@ -15,6 +15,9 @@ export type ChatMessageLocation = {
|
|||
}
|
||||
|
||||
|
||||
const cn = (className: string) => className?.split(' ').map(c => c ? `void-${c}` : '').join(' ')
|
||||
|
||||
|
||||
type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: string }
|
||||
|
||||
const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => {
|
||||
|
|
@ -23,28 +26,93 @@ const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) =>
|
|||
|
||||
|
||||
|
||||
export const CodeSpan = ({ children, className }: { children: React.ReactNode, className?: string }) => {
|
||||
return <code className={`
|
||||
bg-void-bg-1
|
||||
px-1
|
||||
rounded-sm
|
||||
font-mono font-medium
|
||||
break-all
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
// all classnames must go in tailwind.config.js/safelist
|
||||
export const noSpaceStyles = {
|
||||
blockquote: 'pl-4 border-l-4 border-void-bg-2 italic',
|
||||
br: '',
|
||||
code: '',
|
||||
codespan: 'bg-void-bg-1 px-1 rounded-sm font-mono font-medium break-all',
|
||||
def: '',
|
||||
del: 'line-through',
|
||||
em: 'italic',
|
||||
escape: '',
|
||||
heading: {
|
||||
h1: "text-4xl font-semibold pb-2 border-b border-void-bg-2",
|
||||
h2: "text-3xl font-semibold pb-2 border-b border-void-bg-2",
|
||||
h3: "text-2xl font-semibold",
|
||||
h4: "text-xl font-semibold",
|
||||
h5: "text-lg font-semibold",
|
||||
h6: "text-base font-semibold text-gray-600"
|
||||
},
|
||||
hr: 'border-t border-void-bg-2',
|
||||
html: '',
|
||||
image: 'max-w-full h-auto rounded',
|
||||
link: 'underline cursor-pointer',
|
||||
list: 'list-inside pl-2',
|
||||
list_item: '',
|
||||
paragraph: '',
|
||||
space: '',
|
||||
strong: 'font-semibold',
|
||||
table: 'overflow-x-auto',
|
||||
text: '',
|
||||
}
|
||||
|
||||
const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, tokenIdx }: { token: Token | string, nested?: boolean, noSpace?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string }): JSX.Element => {
|
||||
|
||||
const defaultStyles = {
|
||||
blockquote: 'mx-2 pl-4 border-l-4 border-void-bg-2 italic my-4',
|
||||
br: '',
|
||||
code: 'mx-2 my-4',
|
||||
codespan: 'bg-void-bg-1 px-1 rounded-sm font-mono font-medium break-all',
|
||||
def: '',
|
||||
del: 'line-through',
|
||||
em: 'italic',
|
||||
escape: '',
|
||||
heading: {
|
||||
h1: 'mx-2 text-4xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2',
|
||||
h2: 'mx-2 text-3xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2',
|
||||
h3: 'mx-2 text-2xl font-semibold mt-6 mb-4',
|
||||
h4: 'mx-2 text-xl font-semibold mt-6 mb-4',
|
||||
h5: 'mx-2 text-lg font-semibold mt-6 mb-4',
|
||||
h6: 'mx-2 text-base font-semibold mt-6 mb-4 text-gray-600'
|
||||
},
|
||||
hr: 'mx-2 my-6 border-t border-void-bg-2',
|
||||
html: 'mx-2 my-4',
|
||||
image: 'mx-2 my-4 max-w-full h-auto rounded',
|
||||
link: 'mx-2 underline',
|
||||
list: 'mx-2 my-2 list-inside pl-2',
|
||||
list_item: 'mx-2 mb-2',
|
||||
paragraph: 'mx-2 my-4',
|
||||
space: '',
|
||||
strong: 'mx-2 font-semibold',
|
||||
table: 'mx-2 my-4 overflow-x-auto',
|
||||
text: '',
|
||||
}
|
||||
|
||||
|
||||
|
||||
type TokenClasses = typeof defaultStyles
|
||||
|
||||
const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, classes }: { token: Token | string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string, classes?: TokenClasses }): JSX.Element => {
|
||||
|
||||
|
||||
// deal with built-in tokens first (assume marked token)
|
||||
const t = token as MarkedToken
|
||||
|
||||
if(t.raw.trim() ===''){
|
||||
return <></>;
|
||||
}
|
||||
|
||||
// compute the className
|
||||
const defaultClassName = defaultStyles[t.type]
|
||||
const classNameOverride = classes?.[t.type]
|
||||
const _className = classNameOverride ?? defaultClassName
|
||||
let className: string = ''
|
||||
if (typeof defaultClassName === 'string') {
|
||||
className = _className as string
|
||||
}
|
||||
|
||||
if (t.type === "space") {
|
||||
return <span>{t.raw}</span>
|
||||
return <span className={cn(className)}>{t.raw}</span>
|
||||
}
|
||||
|
||||
if (t.type === "code") {
|
||||
|
|
@ -55,32 +123,28 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke
|
|||
tokenIdx: tokenIdx,
|
||||
}) : null
|
||||
|
||||
return <div className='my-4'>
|
||||
return <div className={cn(className)}>
|
||||
<BlockCode
|
||||
initValue={t.text}
|
||||
language={t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]}
|
||||
buttonsOnHover={applyBoxId && <ApplyBlockHoverButtons applyBoxId={applyBoxId} codeStr={t.text} />}
|
||||
/>
|
||||
initValue={t.text}
|
||||
language={t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]}
|
||||
buttonsOnHover={applyBoxId && <ApplyBlockHoverButtons applyBoxId={applyBoxId} codeStr={t.text} />}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
if (t.type === "heading") {
|
||||
const HeadingTag = `h${t.depth}` as keyof JSX.IntrinsicElements
|
||||
const headingClasses: { [h: string]: string } = {
|
||||
h1: "text-4xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2",
|
||||
h2: "text-3xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2",
|
||||
h3: "text-2xl font-semibold mt-6 mb-4",
|
||||
h4: "text-xl font-semibold mt-6 mb-4",
|
||||
h5: "text-lg font-semibold mt-6 mb-4",
|
||||
h6: "text-base font-semibold mt-6 mb-4 text-gray-600"
|
||||
}
|
||||
return <HeadingTag className={headingClasses[HeadingTag]}>{t.text}</HeadingTag>
|
||||
|
||||
const HeadingTag = `h${t.depth}` as keyof typeof defaultStyles.heading
|
||||
|
||||
const className = classes?.heading[HeadingTag] ?? defaultStyles.heading[HeadingTag]
|
||||
|
||||
return <HeadingTag className={cn(className)}>{t.text}</HeadingTag>
|
||||
}
|
||||
|
||||
if (t.type === "table") {
|
||||
return (
|
||||
<div className={`${noSpace ? '' : 'my-4'} overflow-x-auto`}>
|
||||
<table className="min-w-full border border-void-bg-2">
|
||||
<div className={cn(className)}>
|
||||
<table className={"min-w-full border border-void-bg-2"}>
|
||||
<thead>
|
||||
<tr className="bg-void-bg-1">
|
||||
{t.header.map((cell: any, index: number) => (
|
||||
|
|
@ -100,7 +164,7 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke
|
|||
{row.map((cell: any, cellIndex: number) => (
|
||||
<td
|
||||
key={cellIndex}
|
||||
className="px-4 py-2 border border-void-bg-2"
|
||||
className={"px-4 py-2 border border-void-bg-2"}
|
||||
style={{ textAlign: t.align[cellIndex] || "left" }}
|
||||
>
|
||||
{cell.raw}
|
||||
|
|
@ -115,22 +179,34 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke
|
|||
}
|
||||
|
||||
if (t.type === "hr") {
|
||||
return <hr className="my-6 border-t border-void-bg-2" />
|
||||
return <hr className={cn(className)} />
|
||||
}
|
||||
|
||||
if (t.type === "blockquote") {
|
||||
return <blockquote className={`pl-4 border-l-4 border-void-bg-2 italic ${noSpace ? '' : 'my-4'}`}>{t.text}</blockquote>
|
||||
return <blockquote className={cn(className)}>{t.text}</blockquote>
|
||||
}
|
||||
|
||||
if (t.type === 'list_item') {
|
||||
<li className={cn(className)}>
|
||||
<span className="ml-1">
|
||||
!!!!!!!!!!!!!
|
||||
<ChatMarkdownRender chatMessageLocationForApply={chatMessageLocationForApply} string={t.text} nested={true} />
|
||||
</span>
|
||||
</li>
|
||||
}
|
||||
|
||||
if (t.type === "list") {
|
||||
const ListTag = t.ordered ? "ol" : "ul"
|
||||
|
||||
const itemClassName = classes?.['list_item'] ?? defaultStyles['list_item']
|
||||
|
||||
return (
|
||||
<ListTag
|
||||
start={t.start ? t.start : undefined}
|
||||
className={`list-inside pl-2 ${noSpace ? '' : 'my-4'} ${t.ordered ? "list-decimal" : "list-disc"}`}
|
||||
className={`${cn(className)} ${t.ordered ? "list-decimal" : "list-disc"}`}
|
||||
>
|
||||
{t.items.map((item, index) => (
|
||||
<li key={index} className={`${noSpace ? '' : 'mb-4'}`}>
|
||||
<li key={index} className={cn(itemClassName)}>
|
||||
{item.task && (
|
||||
<input type="checkbox" checked={item.checked} readOnly className="mr-2 form-checkbox" />
|
||||
)}
|
||||
|
|
@ -145,10 +221,10 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke
|
|||
// return (
|
||||
// <ListTag
|
||||
// start={t.start ? t.start : undefined}
|
||||
// className={`pl-2 ${noSpace ? '' : 'my-4'} ${t.ordered ? "list-decimal" : "list-disc"}`}
|
||||
// className={`${className} ${t.ordered ? "list-decimal" : "list-disc"}`}
|
||||
// >
|
||||
// {t.items.map((item, index) => (
|
||||
// <li key={index} className={`${noSpace ? '' : 'mb-2'} ml-4`}>
|
||||
// <li key={index} className={`itemClassName`}>
|
||||
// {item.task && (
|
||||
// <input type="checkbox" className='mr-2 form-checkbox' checked={item.checked} readOnly />
|
||||
// )}
|
||||
|
|
@ -164,26 +240,26 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke
|
|||
if (t.type === "paragraph") {
|
||||
const contents = <>
|
||||
{t.tokens.map((token, index) => (
|
||||
<RenderToken key={index} token={token} tokenIdx={`${tokenIdx ? `${tokenIdx}-` : ''}${index}`} /> // assign a unique tokenId to nested components
|
||||
<RenderToken key={index} token={token} tokenIdx={`${tokenIdx ? `${tokenIdx}-` : ''}${index}`} classes={classes} /> // assign a unique tokenId to nested components
|
||||
))}
|
||||
</>
|
||||
if (nested) return contents
|
||||
|
||||
return <p className={`${noSpace ? '' : 'my-4'}`}>
|
||||
return <p className={cn(className)}>
|
||||
{contents}
|
||||
</p>
|
||||
}
|
||||
|
||||
if (t.type === "html") {
|
||||
return (
|
||||
<p className={`${noSpace ? '' : 'my-4'}`}>
|
||||
<p className={cn(className)}>
|
||||
{t.raw}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
if (t.type === "text" || t.type === "escape") {
|
||||
return <span>{t.raw}</span>
|
||||
return <span className={cn(className)}>{t.raw}</span>
|
||||
}
|
||||
|
||||
if (t.type === "def") {
|
||||
|
|
@ -193,7 +269,7 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke
|
|||
if (t.type === "link") {
|
||||
return (
|
||||
<a
|
||||
className='underline'
|
||||
className={cn(className)}
|
||||
onClick={() => { window.open(t.href) }}
|
||||
href={t.href}
|
||||
title={t.title ?? undefined}
|
||||
|
|
@ -208,51 +284,52 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke
|
|||
src={t.href}
|
||||
alt={t.text}
|
||||
title={t.title ?? undefined}
|
||||
className={`max4w-full h-auto rounded ${noSpace ? '' : 'my-4'}`}
|
||||
className={cn(className)}
|
||||
/>
|
||||
}
|
||||
|
||||
if (t.type === "strong") {
|
||||
return <strong className="font-semibold">{t.text}</strong>
|
||||
return <strong className={cn(className)}>{t.text}</strong>
|
||||
}
|
||||
|
||||
if (t.type === "em") {
|
||||
return <em className="italic">{t.text}</em>
|
||||
return <em className={cn(className)}>{t.text}</em>
|
||||
}
|
||||
|
||||
// inline code
|
||||
if (t.type === "codespan") {
|
||||
return (
|
||||
<CodeSpan>
|
||||
<code className={cn(className)}>
|
||||
{t.text}
|
||||
</CodeSpan>
|
||||
</code>
|
||||
)
|
||||
}
|
||||
|
||||
if (t.type === "br") {
|
||||
return <br />
|
||||
return <br className={cn(className)} />
|
||||
}
|
||||
|
||||
// strikethrough
|
||||
if (t.type === "del") {
|
||||
return <del className="line-through">{t.text}</del>
|
||||
return <del className={cn(className)}>{t.text}</del>
|
||||
}
|
||||
|
||||
// default
|
||||
return (
|
||||
<div className="bg-orange-50 rounded-sm overflow-hidden p-2">
|
||||
<span className="text-sm text-orange-500">Unknown type:</span>
|
||||
{t.type}
|
||||
{t.raw}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ChatMarkdownRender = ({ string, nested = false, noSpace, chatMessageLocationForApply }: { string: string, nested?: boolean, noSpace?: boolean, chatMessageLocationForApply?: ChatMessageLocation }) => {
|
||||
export const ChatMarkdownRender = ({ string, nested = false, classes, chatMessageLocationForApply }: { string: string, nested?: boolean, classes?: TokenClasses, chatMessageLocationForApply?: ChatMessageLocation }) => {
|
||||
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
|
||||
return (
|
||||
<>
|
||||
{tokens.map((token, index) => (
|
||||
<RenderToken key={index} token={token} nested={nested} noSpace={noSpace} chatMessageLocationForApply={chatMessageLocationForApply} tokenIdx={index + ''} />
|
||||
<RenderToken key={index} token={token} nested={nested} classes={classes} chatMessageLocationForApply={chatMessageLocationForApply} tokenIdx={index + ''} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
//!!!! merged
|
||||
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { isWindows, isLinux, isMacintosh } from '../../../../../../../base/commo
|
|||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
import { env } from '../../../../../../../base/common/process.js'
|
||||
import { ModelDropdown } from './ModelDropdown.js'
|
||||
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
|
||||
import { ChatMarkdownRender, noSpaceStyles } from '../markdown/ChatMarkdownRender.js'
|
||||
import { WarningBox } from './WarningBox.js'
|
||||
import { os } from '../../../../common/helpers/systemInfo.js'
|
||||
|
||||
|
|
@ -293,7 +293,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
|||
isPasswordField={isPasswordField}
|
||||
/>
|
||||
{subTextMd === undefined ? null : <div className='py-1 px-3 opacity-50 text-sm'>
|
||||
<ChatMarkdownRender noSpace string={subTextMd} />
|
||||
<ChatMarkdownRender classes={noSpaceStyles} string={subTextMd} />
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
|
|
@ -413,11 +413,11 @@ export const FeaturesTab = () => {
|
|||
{/* <h3 className={`mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3> */}
|
||||
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3>
|
||||
<div className='pl-4 opacity-50'>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender noSpace string={`1. Download [Ollama](https://ollama.com/download).`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender noSpace string={`2. Open your terminal.`} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender noSpace string={`3. Run \`ollama run llama3.1:8b\`. This installs Meta's llama3.1 model which is best for chat and inline edits. Requires 5GB of memory.`} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender noSpace string={`4. Run \`ollama run qwen2.5-coder:1.5b\`. This installs a faster autocomplete model. Requires 1GB of memory.`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender noSpace string={`Void automatically detects locally running models and enables them.`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender classes={noSpaceStyles} string={`1. Download [Ollama](https://ollama.com/download).`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender classes={noSpaceStyles} string={`2. Open your terminal.`} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender classes={noSpaceStyles} string={`3. Run \`ollama run llama3.1:8b\`. This installs Meta's llama3.1 model which is best for chat and inline edits. Requires 5GB of memory.`} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender classes={noSpaceStyles} string={`4. Run \`ollama run qwen2.5-coder:1.5b\`. This installs a faster autocomplete model. Requires 1GB of memory.`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender classes={noSpaceStyles} string={`Void automatically detects locally running models and enables them.`} /></span>
|
||||
{/* TODO we should create UI for downloading models without user going into terminal */}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -167,6 +167,82 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
plugins: [],
|
||||
prefix: 'void-'
|
||||
prefix: 'void-',
|
||||
safelist: [
|
||||
// Background colors
|
||||
'void-bg-void-bg-1',
|
||||
|
||||
// Borders
|
||||
'void-border-b',
|
||||
'void-border-l-4',
|
||||
'void-border-t',
|
||||
'void-border-void-bg-2',
|
||||
|
||||
// Typography
|
||||
'void-text-2xl',
|
||||
'void-text-3xl',
|
||||
'void-text-4xl',
|
||||
'void-text-base',
|
||||
'void-text-lg',
|
||||
'void-text-xl',
|
||||
'void-text-gray-600',
|
||||
'void-font-medium',
|
||||
'void-font-mono',
|
||||
'void-font-semibold',
|
||||
'void-italic',
|
||||
'void-line-through',
|
||||
'void-underline',
|
||||
|
||||
// Spacing
|
||||
'void-mt-1',
|
||||
'void-mt-2',
|
||||
'void-mt-4',
|
||||
'void-mt-6',
|
||||
'void-mb-1',
|
||||
'void-mb-2',
|
||||
'void-mb-4',
|
||||
'void-mx-1',
|
||||
'void-mx-2',
|
||||
'void-mx-4',
|
||||
'void-my-1',
|
||||
'void-my-2',
|
||||
'void-my-4',
|
||||
'void-my-6',
|
||||
'void-pb-1',
|
||||
'void-pb-2',
|
||||
'void-pb-4',
|
||||
'void-pl-1',
|
||||
'void-pl-2',
|
||||
'void-pl-4',
|
||||
'void-px-1',
|
||||
'void-px-2',
|
||||
'void-px-4',
|
||||
|
||||
// Sizing and layout
|
||||
'void-h-auto',
|
||||
'void-max-w-full',
|
||||
'void-overflow-x-auto',
|
||||
|
||||
// Lists
|
||||
'void-list-inside',
|
||||
'void-list-decimal',
|
||||
'void-list-disc',
|
||||
|
||||
// Effects and decoration
|
||||
'void-cursor-pointer',
|
||||
'void-ring-8',
|
||||
'void-ring-[#123456]',
|
||||
'void-rounded',
|
||||
'void-rounded-sm',
|
||||
|
||||
// misc
|
||||
'void-break-all',
|
||||
'void-bg-void-bg-1',
|
||||
'void-px-1',
|
||||
'void-rounded-sm',
|
||||
'void-font-mono',
|
||||
'void-font-medium',
|
||||
'void-break-all'
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue