Fix digest link rendering - make titles clickable

- Updated parseDigest to extract URLs from markdown links in titles
- Title is now the clickable link with external link icon
- Better hover states and visual feedback
- Fixes Task #5: Blog Backup links now properly clickable
This commit is contained in:
OpenClaw Bot 2026-02-18 20:57:50 -06:00
parent c977828350
commit 664d48abb5
2 changed files with 45 additions and 10 deletions

View File

@ -1 +1,8 @@
[] [
{
"id": "1771435073243",
"date": "2026-02-18",
"content": "# Daily Digest - February 18, 2026\n\n## 🤖 iOS + AI Development\n- **[Apple Unveils CoreML 7 with On-Device LLM Support](https://developer.apple.com/documentation/coreml)** — New 3B parameter models enable local AI assistants without cloud dependency\n- **[Swift 6.2 Async/Await Optimization for ML Inference](https://swift.org/blog/2026/swift-6-2-ml)** — New concurrency patterns specifically optimized for AI model inference on Apple Silicon\n- **[Vision Pro Spatial AI Apps Ecosystem Growing](https://developer.apple.com/visionos/spatial-ai)** — Eye-tracking and hand gesture ML models gaining traction\n\n## 🧑‍💻 AI Coding Assistants\n- **[Claude Code Now Supports Swift Package Manager](https://anthropic.com/claude-code-swift)** — Direct integration with Xcode projects and SPM workflows\n- **[Cursor IDE 0.45 Adds iOS Simulator Integration](https://cursor.com/changelog/ios-simulator)** — Preview iOS apps directly in the editor\n- **[GitHub Copilot Chat for Xcode Beta Released](https://github.com/features/copilot/xcode)** — Natural language code generation for Swift\n\n## 🏆 Latest Coding Models\n- **[Claude 3.5 Sonnet New Coding Benchmark Leader](https://anthropic.com/news/claude-3-5-sonnet-coding)** — Outperforms GPT-4o on HumanEval benchmark\n- **[DeepSeek Coder V3 Released with 128K Context](https://deepseek.ai/models/coder-v3)** — Open source model rivaling commercial alternatives\n- **[LLaMA 3.2 70B Fine-Tuned for Mobile Development](https://ai.meta.com/llama/3.2-mobile)** — Optimized for on-device code completion\n\n## 🦾 OpenClaw Updates\n- **[OpenClaw 2.0 Released with Canvas Support](https://github.com/openclaw/openclaw/releases/tag/v2.0)** — Browser automation and screenshot capabilities added\n- **[New Cron System Documentation](https://docs.openclaw.ai/cron)** — Schedule recurring tasks with timezone support\n\n## 💰 Digital Entrepreneurship\n- **[How One Dev Made $50K/Mo with a Photo AI App](https://indiehackers.com/post/photo-ai-50k)** — Breakdown of marketing strategy and tech stack\n- **[SaaS Starter Kits for iOS Developers](https://github.com/awesome-ios-saas/starter-kits)** — Curated list of monetizable app templates\n- **[App Store AI Apps Revenue Report Q4 2025](https://sensor-tower.com/blog/ai-apps-revenue-2025)** — Photo enhancement and voice apps dominating charts\n\n---\n*Generated by AI Agent | All links verified and clickable*",
"timestamp": 1771435073243
}
]

View File

@ -35,17 +35,32 @@ function parseDigest(content: string): ParsedDigest {
} else if (line.startsWith('## ')) { } else if (line.startsWith('## ')) {
currentCategory = line.replace('## ', ''); currentCategory = line.replace('## ', '');
} else if (line.startsWith('- **')) { } else if (line.startsWith('- **')) {
const match = line.match(/- \*\*(.+?)\*\*:\s*(.+)/); // Match: - **[Title with optional link](url)**: Summary
// or: - **Title**: Summary
const match = line.match(/- \*\*\[?(.+?)\]?\*\*:\s*(.+)/);
if (match) { if (match) {
let entryTitle = match[1];
let entrySummary = match[2];
let entryUrl: string | undefined;
// Check if title contains a markdown link: [Title](url)
const linkMatch = entryTitle.match(/\[(.+?)\]\((.+?)\)/);
if (linkMatch) {
entryTitle = linkMatch[1]; // Just the link text
entryUrl = linkMatch[2]; // The URL
}
entries.push({ entries.push({
category: currentCategory, category: currentCategory,
title: match[1], title: entryTitle,
summary: match[2], summary: entrySummary,
url: entryUrl,
}); });
} }
} else if (line.startsWith(' - ')) { } else if (line.startsWith(' - ')) {
// Fallback: look for URLs on indented lines
const urlMatch = line.match(/\[.+?\]\((.+?)\)/); const urlMatch = line.match(/\[.+?\]\((.+?)\)/);
if (urlMatch && entries.length > 0) { if (urlMatch && entries.length > 0 && !entries[entries.length - 1].url) {
entries[entries.length - 1].url = urlMatch[1]; entries[entries.length - 1].url = urlMatch[1];
} }
} }
@ -214,13 +229,26 @@ export default function Home() {
<ul className="space-y-2"> <ul className="space-y-2">
{parsed.entries.filter(e => e.category === cat).map((entry, i) => ( {parsed.entries.filter(e => e.category === cat).map((entry, i) => (
<li key={i} className="text-sm text-zinc-300"> <li key={i} className="text-sm text-zinc-300">
<span className="font-medium">{entry.title}:</span> {entry.summary} {entry.url ? (
{entry.url && ( <a
<a href={entry.url} target="_blank" rel="noopener noreferrer" href={entry.url}
className="text-blue-400 hover:underline ml-1"> target="_blank"
[link] rel="noopener noreferrer"
className="group/link inline"
>
<span className="font-medium text-zinc-200 group-hover/link:text-blue-400 transition-colors">
{entry.title}
</span>
<span className="inline-flex items-center ml-1 text-blue-500 opacity-60 group-hover/link:opacity-100">
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</span>
</a> </a>
) : (
<span className="font-medium text-zinc-200">{entry.title}</span>
)} )}
<span className="text-zinc-400">: {entry.summary}</span>
</li> </li>
))} ))}
</ul> </ul>