fix: support inline formatting in table cells (bold, italic, code, links)
This commit is contained in:
@@ -130,7 +130,21 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{#each segment.headers as header}
|
{#each segment.headers as header}
|
||||||
<th>{header}</th>
|
<th>
|
||||||
|
{#each header as cellSeg}
|
||||||
|
{#if cellSeg.type === 'bold'}
|
||||||
|
<strong>{cellSeg.content}</strong>
|
||||||
|
{:else if cellSeg.type === 'italic'}
|
||||||
|
<em>{cellSeg.content}</em>
|
||||||
|
{:else if cellSeg.type === 'code'}
|
||||||
|
<code class="inline-code">{cellSeg.content}</code>
|
||||||
|
{:else if cellSeg.type === 'link'}
|
||||||
|
<a href={cellSeg.href} target="_blank" rel="noopener noreferrer">{cellSeg.content}</a>
|
||||||
|
{:else}
|
||||||
|
{cellSeg.content}
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</th>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -138,7 +152,21 @@
|
|||||||
{#each segment.rows as row}
|
{#each segment.rows as row}
|
||||||
<tr>
|
<tr>
|
||||||
{#each row as cell}
|
{#each row as cell}
|
||||||
<td>{cell}</td>
|
<td>
|
||||||
|
{#each cell as cellSeg}
|
||||||
|
{#if cellSeg.type === 'bold'}
|
||||||
|
<strong>{cellSeg.content}</strong>
|
||||||
|
{:else if cellSeg.type === 'italic'}
|
||||||
|
<em>{cellSeg.content}</em>
|
||||||
|
{:else if cellSeg.type === 'code'}
|
||||||
|
<code class="inline-code">{cellSeg.content}</code>
|
||||||
|
{:else if cellSeg.type === 'link'}
|
||||||
|
<a href={cellSeg.href} target="_blank" rel="noopener noreferrer">{cellSeg.content}</a>
|
||||||
|
{:else}
|
||||||
|
{cellSeg.content}
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -345,60 +373,6 @@
|
|||||||
border: 1px solid rgba(251, 191, 36, 0.3);
|
border: 1px solid rgba(251, 191, 36, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.thinking .message-content {
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border: 1px dashed rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-time {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
color: #666;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thinking-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thinking-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #888;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thinking-toggle:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.thinking-icon {
|
|
||||||
font-size: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thinking-label {
|
|
||||||
font-weight: 500;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thinking-content {
|
|
||||||
color: #999;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-code {
|
.inline-code {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
padding: 0.15rem 0.4rem;
|
padding: 0.15rem 0.4rem;
|
||||||
@@ -430,11 +404,6 @@
|
|||||||
margin: 0.25rem 0;
|
margin: 0.25rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
color: #667eea;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-heading {
|
.content-heading {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -481,10 +450,22 @@
|
|||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-time {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.typing {
|
.typing {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|||||||
@@ -3,12 +3,18 @@
|
|||||||
* Supports: bold, italic, code blocks, inline code, links, lists, tables, headings, line breaks
|
* Supports: bold, italic, code blocks, inline code, links, lists, tables, headings, line breaks
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
interface InlineSegment {
|
||||||
|
type: 'text' | 'bold' | 'italic' | 'code' | 'link';
|
||||||
|
content: string;
|
||||||
|
href?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface ParsedSegment {
|
interface ParsedSegment {
|
||||||
type: 'text' | 'bold' | 'italic' | 'code' | 'codeBlock' | 'link' | 'list' | 'table' | 'lineBreak' | 'heading';
|
type: 'text' | 'bold' | 'italic' | 'code' | 'codeBlock' | 'link' | 'list' | 'table' | 'lineBreak' | 'heading';
|
||||||
content: string;
|
content: string;
|
||||||
items?: string[];
|
items?: string[];
|
||||||
headers?: string[];
|
headers?: InlineSegment[][];
|
||||||
rows?: string[][];
|
rows?: InlineSegment[][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseMarkdown(text: string): ParsedSegment[] {
|
export function parseMarkdown(text: string): ParsedSegment[] {
|
||||||
@@ -79,7 +85,7 @@ function parseTable(tableStr: string): ParsedSegment[] {
|
|||||||
if (lines.length < 2) return [];
|
if (lines.length < 2) return [];
|
||||||
|
|
||||||
// Skip separator line (|---|---|)
|
// Skip separator line (|---|---|)
|
||||||
const dataLines = lines.filter(line => !line.match(/^[\|\s-]+$/));
|
const dataLines = lines.filter(line => !line.match(/^[\|\s\-:]+$/));
|
||||||
if (dataLines.length < 2) return [];
|
if (dataLines.length < 2) return [];
|
||||||
|
|
||||||
const headers = parseTableRow(dataLines[0]);
|
const headers = parseTableRow(dataLines[0]);
|
||||||
@@ -93,8 +99,39 @@ function parseTable(tableStr: string): ParsedSegment[] {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTableRow(row: string): string[] {
|
function parseTableRow(row: string): InlineSegment[][] {
|
||||||
return row.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
|
return row.split('|')
|
||||||
|
.map(cell => cell.trim())
|
||||||
|
.filter(cell => cell !== '')
|
||||||
|
.map(cell => parseInlineElements(cell));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseInlineElements(text: string): InlineSegment[] {
|
||||||
|
const segments: InlineSegment[] = [];
|
||||||
|
|
||||||
|
const inlineRegex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[.*?\]\(.*?\))/g;
|
||||||
|
const parts = text.split(inlineRegex);
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!part) continue;
|
||||||
|
|
||||||
|
if (part.startsWith('**') && part.endsWith('**')) {
|
||||||
|
segments.push({ type: 'bold', content: part.slice(2, -2) });
|
||||||
|
} else if (part.startsWith('*') && part.endsWith('*')) {
|
||||||
|
segments.push({ type: 'italic', content: part.slice(1, -1) });
|
||||||
|
} else if (part.startsWith('`') && part.endsWith('`')) {
|
||||||
|
segments.push({ type: 'code', content: part.slice(1, -1) });
|
||||||
|
} else if (part.startsWith('[') && part.includes('](')) {
|
||||||
|
const linkMatch = part.match(/\[(.*?)\]\((.*?)\)/);
|
||||||
|
if (linkMatch) {
|
||||||
|
segments.push({ type: 'link', content: linkMatch[1], href: linkMatch[2] });
|
||||||
|
}
|
||||||
|
} else if (part) {
|
||||||
|
segments.push({ type: 'text', content: part });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseLines(text: string): ParsedSegment[] {
|
function parseLines(text: string): ParsedSegment[] {
|
||||||
@@ -149,7 +186,7 @@ function parseLines(text: string): ParsedSegment[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process inline formatting
|
// Process inline formatting
|
||||||
const inlineSegments = parseInlineElements(line);
|
const inlineSegments = parseInlineElementsAsText(line);
|
||||||
segments.push(...inlineSegments);
|
segments.push(...inlineSegments);
|
||||||
|
|
||||||
// Add line break after non-empty lines (except last in a paragraph)
|
// Add line break after non-empty lines (except last in a paragraph)
|
||||||
@@ -161,7 +198,7 @@ function parseLines(text: string): ParsedSegment[] {
|
|||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseInlineElements(text: string): ParsedSegment[] {
|
function parseInlineElementsAsText(text: string): ParsedSegment[] {
|
||||||
const segments: ParsedSegment[] = [];
|
const segments: ParsedSegment[] = [];
|
||||||
|
|
||||||
const inlineRegex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[.*?\]\(.*?\))/g;
|
const inlineRegex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[.*?\]\(.*?\))/g;
|
||||||
|
|||||||
Reference in New Issue
Block a user