Skip to content
This repository was archived by the owner on Jun 28, 2026. It is now read-only.

Commit 10bd0da

Browse files
Merge pull request #318 from olasunkanmi-SE/feature_news_update
feat(news): add save/delete functionality and daily cleanup
2 parents 775e6d1 + 82c755a commit 10bd0da

17 files changed

Lines changed: 545 additions & 84 deletions

docs/DOCUMENTATION_SITE_ROADMAP.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# CodeBuddy Documentation Site Roadmap
2+
3+
## Goal
4+
Establish a world-class, developer-centric documentation hub for CodeBuddy, featuring an "OpenAI-style" minimalist aesthetic, hosted on GitHub Pages.
5+
6+
## Tech Stack
7+
- **Framework:** [Nextra](https://nextra.site/) (Next.js + MDX).
8+
- *Why:* Clean, "Vercel-style" design out-of-the-box, React-based (consistent with CodeBuddy), excellent performance, and built-in full-text search.
9+
- **Styling:** Tailwind CSS.
10+
- **Hosting:** GitHub Pages.
11+
- **Package Manager:** npm.
12+
13+
## Phase 1: Infrastructure & Scaffolding (Immediate)
14+
1. **Initialize Project:** Create a `website/` directory in the repo root.
15+
2. **Install Dependencies:** `next`, `react`, `react-dom`, `nextra`.
16+
3. **Configuration:** Set up `next.config.js` and `theme.config.jsx`.
17+
4. **Structure:**
18+
- `website/pages/index.mdx` (Landing Page)
19+
- `website/pages/docs/` (Documentation Root)
20+
- `website/pages/blog/` (Blog Root)
21+
22+
## Phase 2: Content Migration
23+
1. **Architecture Docs:** Move existing `docs/*.md` files (e.g., `KNOWLEDGE_GRAPH_IMPLEMENTATION.md`) into `website/pages/docs/architecture/`.
24+
2. **Getting Started:** Create `website/pages/docs/getting-started.mdx` derived from `README.md`.
25+
3. **Blog Setup:** Initialize the blog section for "OpenAI-style" announcements.
26+
27+
## Phase 3: CI/CD & Deployment
28+
1. **GitHub Actions:** Create `.github/workflows/deploy-site.yml`.
29+
2. **Build Script:** Configure static export (`output: 'export'` in Next.js).
30+
3. **Domain:** Configure `codebuddy.github.io` (or custom domain).
31+
32+
## Phase 4: Branding & Polish
33+
1. **Typography:** Configure Inter/SF Pro fonts.
34+
2. **Theming:** Customize colors to match CodeBuddy's dark/light theme preferences.
35+
3. **Interactive Elements:** Add "Copy Code" buttons and syntax highlighting (built-in to Nextra).
36+
37+
## Next Steps
38+
- [ ] Initialize `website/` directory with Nextra template.
39+
- [ ] Migrate first batch of markdown files.

src/services/news-reader.service.ts

Lines changed: 166 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ export class NewsReaderService implements vscode.Disposable {
1212
private currentPanel: vscode.WebviewPanel | undefined;
1313
private cacheManager: EnhancedCacheManager;
1414

15+
// Expose current article for Agent context
16+
public currentArticle:
17+
| { title: string; content: string; url: string }
18+
| undefined;
19+
1520
private constructor() {
1621
this.logger = Logger.initialize("NewsReaderService", {});
1722
this.cacheManager = new EnhancedCacheManager({
@@ -47,12 +52,45 @@ export class NewsReaderService implements vscode.Disposable {
4752
this.logger.info("Reader cache cleared manually");
4853
}
4954

55+
public async analyzeContent(url: string): Promise<void> {
56+
try {
57+
this.logger.info(`Analyzing content for context: ${url}`);
58+
const article = await this._fetchArticle(url);
59+
if (article) {
60+
this.currentArticle = {
61+
title: article.title,
62+
content: article.textContent,
63+
url: url,
64+
};
65+
}
66+
} catch (error) {
67+
this.logger.warn(`Failed to analyze content for ${url}`, error);
68+
}
69+
}
70+
71+
private async _fetchArticle(url: string): Promise<any> {
72+
const response = await axios.get(url, {
73+
headers: {
74+
"User-Agent":
75+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
76+
},
77+
timeout: 10000, // 10s timeout
78+
});
79+
const html = response.data;
80+
const doc = new JSDOM(html, { url });
81+
const reader = new Readability(doc.window.document);
82+
return reader.parse();
83+
}
84+
5085
public async openReader(url: string, title?: string): Promise<void> {
5186
try {
5287
// 1. Check cache
5388
const cachedHtml = await this.cacheManager.getResponse(url);
5489
if (cachedHtml) {
5590
this.logger.info(`Cache hit for reader: ${url}`);
91+
// We try to reconstruct context from cache if possible, or just skip it.
92+
// Since we don't store metadata separately, we might miss context on cache hit.
93+
// But if analyzeContent was called before, we might have it.
5694
this.showPanel(cachedHtml, title || "Reader View");
5795
return;
5896
}
@@ -69,24 +107,20 @@ export class NewsReaderService implements vscode.Disposable {
69107
},
70108
async (progress) => {
71109
progress.report({ message: "Fetching content..." });
72-
const response = await axios.get(url, {
73-
headers: {
74-
"User-Agent":
75-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
76-
},
77-
timeout: 10000, // 10s timeout
78-
});
79-
const html = response.data;
80-
81-
progress.report({ message: "Parsing content..." });
82-
const doc = new JSDOM(html, { url });
83-
const reader = new Readability(doc.window.document);
84-
const article = reader.parse();
110+
111+
const article = await this._fetchArticle(url);
85112

86113
if (!article) {
87114
throw new Error("Could not parse article content");
88115
}
89116

117+
// Set current article for context
118+
this.currentArticle = {
119+
title: article.title,
120+
content: article.textContent,
121+
url: url,
122+
};
123+
90124
const readerHtml = this.getReaderHtml(article);
91125

92126
// Cache the result
@@ -125,7 +159,38 @@ export class NewsReaderService implements vscode.Disposable {
125159

126160
this.currentPanel.onDidDispose(() => {
127161
this.currentPanel = undefined;
162+
this.currentArticle = undefined; // Clear context when closed
128163
});
164+
165+
// Handle messages from the webview
166+
this.currentPanel.webview.onDidReceiveMessage(
167+
async (message) => {
168+
switch (message.command) {
169+
case "open":
170+
if (message.url) {
171+
this.openReader(message.url);
172+
}
173+
break;
174+
case "open-new":
175+
if (message.url) {
176+
// Open in a new column/split if possible, or just a new panel
177+
// For now, openReader reuses currentPanel if it exists.
178+
// To support "New Tab", we'd need to manage multiple panels.
179+
// Let's force a new panel by setting currentPanel to undefined temporarily?
180+
// No, that would lose the reference to the old one.
181+
// We need to change how we manage panels to support multiple tabs.
182+
// But for now, let's just open in external browser as "New Tab" equivalent
183+
// OR, we can instantiate a new NewsReaderService? No, it's a singleton.
184+
185+
// Simplest "New Tab" emulation: Open in system browser
186+
vscode.env.openExternal(vscode.Uri.parse(message.url));
187+
}
188+
break;
189+
}
190+
},
191+
undefined,
192+
[],
193+
);
129194
}
130195

131196
this.currentPanel.title = title;
@@ -155,6 +220,11 @@ export class NewsReaderService implements vscode.Disposable {
155220
--text-color: var(--vscode-editor-foreground);
156221
--link-color: var(--vscode-textLink-foreground);
157222
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
223+
--menu-bg: var(--vscode-menu-background);
224+
--menu-fg: var(--vscode-menu-foreground);
225+
--menu-border: var(--vscode-menu-border);
226+
--menu-hover-bg: var(--vscode-menu-selectionBackground);
227+
--menu-hover-fg: var(--vscode-menu-selectionForeground);
158228
}
159229
160230
body {
@@ -218,11 +288,37 @@ export class NewsReaderService implements vscode.Disposable {
218288
a {
219289
color: var(--link-color);
220290
text-decoration: none;
291+
cursor: pointer;
221292
}
222293
223294
a:hover {
224295
text-decoration: underline;
225296
}
297+
298+
/* Custom Context Menu */
299+
#context-menu {
300+
display: none;
301+
position: fixed;
302+
z-index: 1000;
303+
background: var(--menu-bg);
304+
color: var(--menu-fg);
305+
border: 1px solid var(--menu-border);
306+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
307+
border-radius: 4px;
308+
padding: 4px 0;
309+
min-width: 150px;
310+
}
311+
312+
.menu-item {
313+
padding: 6px 12px;
314+
cursor: pointer;
315+
font-size: 13px;
316+
}
317+
318+
.menu-item:hover {
319+
background: var(--menu-hover-bg);
320+
color: var(--menu-hover-fg);
321+
}
226322
</style>
227323
</head>
228324
<body>
@@ -236,6 +332,63 @@ export class NewsReaderService implements vscode.Disposable {
236332
${cleanContent}
237333
</div>
238334
</article>
335+
336+
<div id="context-menu">
337+
<div class="menu-item" id="menu-open-new">Open in System Browser</div>
338+
<div class="menu-item" id="menu-copy-link">Copy Link Address</div>
339+
</div>
340+
341+
<script>
342+
const vscode = acquireVsCodeApi();
343+
const contextMenu = document.getElementById('context-menu');
344+
const openNewItem = document.getElementById('menu-open-new');
345+
const copyLinkItem = document.getElementById('menu-copy-link');
346+
let currentLink = null;
347+
348+
// Handle regular clicks
349+
document.addEventListener('click', (e) => {
350+
// Hide context menu on any click
351+
if (contextMenu.style.display === 'block') {
352+
contextMenu.style.display = 'none';
353+
}
354+
355+
const link = e.target.closest('a');
356+
if (link) {
357+
// If it's a regular click, navigate in reader
358+
e.preventDefault();
359+
vscode.postMessage({ command: 'open', url: link.href });
360+
}
361+
});
362+
363+
// Handle right-click (context menu)
364+
document.addEventListener('contextmenu', (e) => {
365+
const link = e.target.closest('a');
366+
if (link) {
367+
e.preventDefault();
368+
currentLink = link.href;
369+
370+
// Position menu
371+
contextMenu.style.left = e.clientX + 'px';
372+
contextMenu.style.top = e.clientY + 'px';
373+
contextMenu.style.display = 'block';
374+
}
375+
});
376+
377+
// Menu actions
378+
openNewItem.addEventListener('click', () => {
379+
if (currentLink) {
380+
vscode.postMessage({ command: 'open-new', url: currentLink });
381+
contextMenu.style.display = 'none';
382+
}
383+
});
384+
385+
copyLinkItem.addEventListener('click', () => {
386+
if (currentLink) {
387+
navigator.clipboard.writeText(currentLink);
388+
contextMenu.style.display = 'none';
389+
}
390+
});
391+
</script>
239392
</body>
240393
</html>`;
241394
}

0 commit comments

Comments
 (0)