mirror of
https://github.com/dergigi/boris.git
synced 2026-01-31 12:44:37 +01:00
feat(readability): render Markdown when proxy provides it
- Detect markdown blocks from r.jina.ai output - Add react-markdown + remark-gfm for rendering - Extend ContentPanel to render markdown or HTML - Add styles for markdown content
This commit is contained in:
@@ -125,6 +125,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
|
||||
loading={readerLoading}
|
||||
title={readerContent?.title}
|
||||
html={readerContent?.html}
|
||||
markdown={readerContent?.markdown}
|
||||
selectedUrl={selectedUrl}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import React from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
interface ContentPanelProps {
|
||||
loading: boolean
|
||||
title?: string
|
||||
html?: string
|
||||
markdown?: string
|
||||
selectedUrl?: string
|
||||
}
|
||||
|
||||
const ContentPanel: React.FC<ContentPanelProps> = ({ loading, title, html, selectedUrl }) => {
|
||||
const ContentPanel: React.FC<ContentPanelProps> = ({ loading, title, html, markdown, selectedUrl }) => {
|
||||
if (!selectedUrl) {
|
||||
return (
|
||||
<div className="content-panel empty">
|
||||
@@ -27,7 +30,13 @@ const ContentPanel: React.FC<ContentPanelProps> = ({ loading, title, html, selec
|
||||
return (
|
||||
<div className="content-panel">
|
||||
{title && <h2 className="content-title">{title}</h2>}
|
||||
{html ? (
|
||||
{markdown ? (
|
||||
<div className="content-markdown">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{markdown}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
) : html ? (
|
||||
<div className="content-html" dangerouslySetInnerHTML={{ __html: html }} />
|
||||
) : (
|
||||
<div className="content-panel empty">
|
||||
|
||||
@@ -298,6 +298,45 @@ body {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.content-markdown {
|
||||
color: #ddd;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.content-markdown h1,
|
||||
.content-markdown h2,
|
||||
.content-markdown h3,
|
||||
.content-markdown h4 {
|
||||
margin-top: 1.2rem;
|
||||
}
|
||||
|
||||
.content-markdown p {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.content-markdown a {
|
||||
color: #8ab4f8;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.content-markdown a:hover { text-decoration: underline; }
|
||||
|
||||
.content-markdown pre,
|
||||
.content-markdown code {
|
||||
background: #111;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.content-markdown pre {
|
||||
padding: 0.75rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.content-markdown code {
|
||||
padding: 0.1rem 0.3rem;
|
||||
}
|
||||
|
||||
.bookmark-item {
|
||||
background: #1a1a1a;
|
||||
padding: 1.5rem;
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
export interface ReadableContent {
|
||||
url: string
|
||||
title?: string
|
||||
html: string
|
||||
html?: string
|
||||
markdown?: string
|
||||
}
|
||||
|
||||
function toProxyUrl(url: string): string {
|
||||
@@ -19,8 +20,24 @@ export async function fetchReadableContent(targetUrl: string): Promise<ReadableC
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch readable content (${res.status})`)
|
||||
}
|
||||
const html = await res.text()
|
||||
// Best-effort title extraction
|
||||
const text = await res.text()
|
||||
// Detect if the proxy delivered Markdown or HTML. r.jina.ai often returns a
|
||||
// block starting with "Title:" and "Markdown Content:". We handle both.
|
||||
const hasMarkdownBlock = /Markdown Content:\s/i.test(text)
|
||||
|
||||
if (hasMarkdownBlock) {
|
||||
// Try to split out Title and the Markdown payload
|
||||
const titleMatch = text.match(/Title:\s*(.*?)(?:\s+URL Source:|\s+Markdown Content:)/i)
|
||||
const mdMatch = text.match(/Markdown Content:\s*([\s\S]*)$/i)
|
||||
return {
|
||||
url: targetUrl,
|
||||
title: titleMatch?.[1]?.trim(),
|
||||
markdown: mdMatch?.[1]?.trim()
|
||||
}
|
||||
}
|
||||
|
||||
const html = text
|
||||
// Best-effort title extraction from HTML
|
||||
const match = html.match(/<title[^>]*>(.*?)<\/title>/i)
|
||||
return {
|
||||
url: targetUrl,
|
||||
|
||||
Reference in New Issue
Block a user