@@ -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