sondakika 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,4 @@
1
1
  # Sondakika
2
-
3
2
  **Windows için Modern Electron Tabanlı RSS Haber Okuyucu + Eski CLI Aracı**
4
3
  *Türkçe haberleri gerçek zamanlı toplayan yerel masaüstü uygulaması, artı orijinal terminal tabanlı CLI işlevselliği*
5
4
 
@@ -11,8 +10,8 @@
11
10
  ---
12
11
 
13
12
  ## 📋 İçindekiler
14
- 1. [Genel Bakış](#-genel-bakış-1)
15
- 2. [🪟 Windows Masaüstü Uygulaması (v2.0.0 GUI)](#-windows-masaüstü-uygulaması-v200-gui-1)
13
+ 1. [Genel Bakış](#genel-bakış)
14
+ 2. [🪟 Windows Masaüstü Uygulaması (v2.0.0 GUI)](#-windows-masaüstü-uygulaması-v200-gui)
16
15
  - [Sondakika Nedir?](#sondakika-nedir)
17
16
  - [Nasıl Çalışır?](#nasıl-çalışır)
18
17
  - [Sistem Gereksinimleri](#sistem-gereksinimleri)
@@ -29,23 +28,23 @@
29
28
  5. [🛠️ Kaynaktan Derleme](#️-kaynaktan-derleme)
30
29
  6. [📦 v2.0.0'da Neler Yaptık?](#-v200da-neler-yaptık)
31
30
  7. [🤝 Katkıda Bulunma](#-katkıda-bulunma)
32
- 8. [📄 Lisans](#-lisans-1)
31
+ 8. [📄 Lisans](#-lisans)
33
32
 
34
33
  ---
35
34
 
36
- ## 📖 Genel Bakış
37
- Sondakika iki modda mevcuttur:
35
+ ## Genel Bakış
36
+ Sondakika iki modda sunulur:
38
37
  - **v2.0.0+**: Uygulama içi makale okuyucu, temalandırma ve klavye navigasyonu içeren tam özellikli yerel Windows Electron GUI
39
- - **v1.x Eski**: Türk RSS beslemelerinden terminalinizde doğrudan haber getirmek ve görüntülemek için hafif terminal tabanlı CLI aracı
38
+ - **v1.x Eski**: Türk RSS beslemelerinden terminalde doğrudan haber getirmek için hafif terminal tabanlı CLI aracı
40
39
 
41
- Varsayılan `npm start` komutu v2.0.0 Electron GUI'sini çalıştırır. Eski CLI işlevselliği, terminal tabanlı iş akışlarını tercih eden kullanıcılar için korunmuştur (tam CLI kodu için v1.x sürümlerine bakın).
40
+ Varsayılan `npm start` komutu v2.0.0 Electron GUI'sini çalıştırır. Eski CLI işlevselliği, terminal tabanlı iş akışlarını tercih eden kullanıcılar için korunmuştur (tam CLI kodu için [v1.x sürümlerine](https://github.com/eaeoz/sondakika/releases) bakın).
42
41
 
43
42
  ---
44
43
 
45
44
  ## 🪟 Windows Masaüstü Uygulaması (v2.0.0 GUI)
46
45
 
47
46
  ### Sondakika Nedir?
48
- 9 büyük Türk haber kaynağından (Cumhuriyet, TRT Haber, Mynet, Sabah, Star, Gazete Vatan, Habertürk, CNN Türk, Yeni Şafak, Anadolu Ajansı) gerçek zamanlı haberleri modern ve kolay okunabilir bir arayüzde getiren yerel bir Windows uygulaması.
47
+ 9 büyük Türk haber kaynağından (Cumhuriyet, TRT Haber, Mynet, Sabah, Star, Gazete Vatan, Habertürk, CNN Türk, Yeni Şafak, Anadolu Ajansı) gerçek zamanlı haberleri modern ve kolay okunabilir bir arayüzde getiren yerel bir Windows uygulamasıdır.
49
48
 
50
49
  ### Nasıl Çalışır?
51
50
  1. Uygulama, yapılandırılmış Türk haber kaynaklarından RSS beslemelerini almak için `rss-parser` kullanır
@@ -62,7 +61,6 @@ Varsayılan `npm start` komutu v2.0.0 Electron GUI'sini çalıştırır. Eski CL
62
61
  - Haberleri çekmek için internet bağlantısı
63
62
 
64
63
  ### 📥 İndirme
65
-
66
64
  Windows yükleyicisini doğrudan indirin:
67
65
 
68
66
  [![Sondakika 2.0.0 İndir](https://img.shields.io/badge/⬇️%20İndir-Windows%20Yükleyici%20(64-bit)-blue?style=for-the-badge)](https://github.com/eaeoz/sondakika/releases/download/2.0.0/Sondakika.Setup.2.0.0.exe)
@@ -131,6 +129,9 @@ Veya tüm sürümler için (v1.x CLI-only sürümleri dahil) [GitHub Releases sa
131
129
 
132
130
  ---
133
131
 
132
+ ## ⌨️ Eski CLI Kullanımı (v1.x)
133
+ *Not: v2.0.0 kod tabanı Electron GUI'sine odaklanmıştır. Aşağıdaki CLI işlevselliği v1.x sürümlerinden korunmuştur. Tam CLI aracını kullanmak için [v1.x sürümlerine](https://github.com/eaeoz/sondakika/releases) bakın veya `npm install -g sondakika@1.x` ile eski sürümü yükleyin.*
134
+
134
135
  ### CLI Özellikleri
135
136
  - 📰 Birden fazla Türk haber kaynağından haber getir
136
137
  - 🔗 Terminalde tıklanabilir URL'ler (iTerm2, Windows Terminal, macOS Terminal)
@@ -140,12 +141,12 @@ Veya tüm sürümler için (v1.x CLI-only sürümleri dahil) [GitHub Releases sa
140
141
  ### CLI Kurulumu
141
142
  #### npx kullanarak (kurulum gerektirmez, sadece v1.x)
142
143
  ```bash
143
- npx sondakika trt
144
+ npx sondakika@1.x trt
144
145
  ```
145
146
 
146
147
  #### Global kurulum (v1.x)
147
148
  ```bash
148
- npm install -g sondakika
149
+ npm install -g sondakika@1.x
149
150
  ```
150
151
 
151
152
  ### CLI Kullanım Örnekleri
@@ -255,7 +256,6 @@ Eski CLI işlevselliği üzerinde çalışmak için [eski sürümlerdeki](https:
255
256
  ---
256
257
 
257
258
  ## 📦 v2.0.0'da Neler Yaptık?
258
-
259
259
  Bu sürüm, orijinal CLI aracından tam özellikli bir Windows masaüstü uygulamasına tam geçiştir:
260
260
 
261
261
  ### Teknik Uygulama
package/cli.js CHANGED
@@ -2,101 +2,487 @@
2
2
 
3
3
  const blessed = require('blessed');
4
4
  const RssParser = require('rss-parser');
5
- const parser = new RssParser();
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
6
8
 
7
- const sources = {
8
- cumhuriyet: { name: 'Cumhuriyet', url: 'https://www.cumhuriyet.com.tr/rss/son_dakika.xml', type: 'breaking' },
9
- trt: { name: 'TRT Haber', url: 'https://www.trthaber.com/son-dakika.rss', type: 'breaking' },
10
- mynet: { name: 'Mynet', url: 'https://www.mynet.com/rss/son-dakika/', type: 'breaking' },
11
- sabah: { name: 'Sabah', url: 'https://www.sabah.com.tr/rss/son-dakika.xml', type: 'news' },
12
- star: { name: 'Star', url: 'https://www.star.com.tr/rss/son-dakika.xml', type: 'news' },
13
- vatan: { name: 'Gazete Vatan', url: 'https://www.gazetevatan.com/rss/son-dakika.xml', type: 'news' },
14
- haberturk: { name: 'Habertürk', url: 'https://www.haberturk.com/rss/son-dakika.xml', type: 'news' },
15
- cnnturk: { name: 'CNN Türk', url: 'https://www.cnnturk.com/feed/rss', type: 'news' },
16
- yenisafak: { name: 'Yeni Şafak', url: 'https://www.yenisafak.com/rss/son-dakika.xml', type: 'news' },
17
- aa: { name: 'Anadolu Ajansı', url: 'https://www.aa.com.tr/tr/rss/default?cat=guncel', type: 'news' }
9
+ const packagePath = path.join(__dirname, 'package.json');
10
+ const { version } = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
11
+
12
+ const STATE_DIR = path.join(os.homedir(), '.config', 'sondakika-cli');
13
+ const STATE_FILE = path.join(STATE_DIR, 'state.json');
14
+
15
+ const defaultCLIState = {
16
+ lastSource: 'trt',
17
+ itemsPerPage: 10
18
18
  };
19
19
 
20
- const args = process.argv.slice(2);
21
- const sourceArg = args[0];
22
- const countArg = parseInt(args[1]) || 10;
20
+ function loadCLIState() {
21
+ try {
22
+ if (fs.existsSync(STATE_FILE)) {
23
+ const data = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
24
+ return { ...defaultCLIState, ...data };
25
+ }
26
+ } catch (e) {}
27
+ return { ...defaultCLIState };
28
+ }
23
29
 
24
- if (!sourceArg || !sources[sourceArg]) {
25
- console.log(`
26
- 📰 Sondakika - Türkçe Haber Okuyucu
30
+ function saveCLIState(state) {
31
+ try {
32
+ if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
33
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
34
+ } catch (e) {}
35
+ }
27
36
 
28
- Kullanım:
29
- sondakika <kaynak> [adet]
37
+ // Sources with working URLs (mix of HTTP and HTTPS as per original root index.js)
38
+ const sources = {
39
+ cumhuriyet: {
40
+ name: 'Cumhuriyet',
41
+ url: 'https://www.cumhuriyet.com.tr/rss/son_dakika.xml',
42
+ isSondakika: true
43
+ },
44
+ trt: {
45
+ name: 'TRT Haber',
46
+ url: 'https://www.trthaber.com/sondakika.rss',
47
+ isSondakika: true
48
+ },
49
+ mynet: {
50
+ name: 'Mynet',
51
+ url: 'https://www.mynet.com/haber/rss/sondakika',
52
+ isSondakika: true
53
+ },
54
+ sabah: {
55
+ name: 'Sabah',
56
+ url: 'https://www.sabah.com.tr/rss/anasayfa.xml',
57
+ isSondakika: false
58
+ },
59
+ star: {
60
+ name: 'Star',
61
+ url: 'https://www.star.com.tr/rss/rss.asp?cid=124',
62
+ isSondakika: false
63
+ },
64
+ vatan: {
65
+ name: 'Gazete Vatan',
66
+ url: 'https://www.gazetevatan.com/rss/gundem.xml',
67
+ isSondakika: false
68
+ },
69
+ haberturk: {
70
+ name: 'Haberturk',
71
+ url: 'https://www.haberturk.com/rss',
72
+ isSondakika: false
73
+ },
74
+ cnnturk: {
75
+ name: 'CNN Turk',
76
+ url: 'https://cnnturk.com/feed/rss/turkiye',
77
+ isSondakika: false
78
+ },
79
+ yenisafak: {
80
+ name: 'Yeni Safak',
81
+ url: 'https://www.yenisafak.com/rss',
82
+ isSondakika: false
83
+ },
84
+ aa: {
85
+ name: 'Anadolu Ajansi',
86
+ url: 'https://www.aa.com.tr/en/rss',
87
+ isSondakika: false
88
+ }
89
+ };
30
90
 
31
- Mevcut Kaynaklar:
32
- Son Dakika:
33
- cumhuriyet - Cumhuriyet
34
- trt - TRT Haber
35
- mynet - Mynet
36
-
37
- Haberler:
38
- sabah - Sabah
39
- star - Star
40
- vatan - Gazete Vatan
41
- haberturk - Habertürk
42
- cnnturk - CNN Türk
43
- yenisafak - Yeni Şafak
44
- aa - Anadolu Ajansı
91
+ const parser = new RssParser({
92
+ timeout: 10000,
93
+ requestOptions: {
94
+ headers: {
95
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
96
+ }
97
+ },
98
+ maxRedirects: 5
99
+ });
45
100
 
46
- Örnekler:
47
- sondakika trt
48
- sondakika cumhuriyet 20
49
- sondakika haberturk 5
50
- `);
51
- process.exit(0);
101
+ const ITEMS_PER_PAGE = 10;
102
+
103
+ function formatDate(pubDate) {
104
+ if (!pubDate) return '';
105
+ const date = new Date(pubDate);
106
+ if (isNaN(date.getTime())) return '';
107
+ return date.toLocaleDateString('tr-TR', {
108
+ day: '2-digit',
109
+ month: '2-digit',
110
+ year: 'numeric',
111
+ hour: '2-digit',
112
+ minute: '2-digit'
113
+ });
52
114
  }
53
115
 
54
- const source = sources[sourceArg];
116
+ async function fetchNews(sourceKey, count) {
117
+ const source = sources[sourceKey.toLowerCase()];
118
+ if (!source) {
119
+ console.error(`Unknown source: ${sourceKey}`);
120
+ console.log(`Available sources: ${Object.keys(sources).join(', ')}`);
121
+ process.exit(1);
122
+ }
55
123
 
56
- async function fetchNews() {
57
124
  try {
58
125
  const feed = await parser.parseURL(source.url);
59
- const items = feed.items.slice(0, countArg);
60
-
61
- const screen = blessed.screen({ smartCSR: true });
62
- const box = blessed.box({
63
- top: 0,
64
- left: 0,
65
- width: '100%',
66
- height: '100%',
67
- content: `{center}📰 ${source.name} - Son Dakika{/center}`,
68
- tags: true,
69
- border: { type: 'line' },
70
- style: { border: { fg: 'cyan' } }
71
- });
72
-
73
- screen.append(box);
74
- screen.render();
75
- screen.destroy();
76
- screen.program.destroy();
77
-
78
- console.log(`\n📰 ══════════════════════════════════════════════════`);
79
- console.log(` ${source.name} - Son ${countArg} Haber`);
80
- console.log(` ══════════════════════════════════════════════════\n`);
81
-
82
- items.forEach((item, index) => {
83
- const date = item.pubDate ? new Date(item.pubDate).toLocaleString('tr-TR') : '';
84
- console.log(` ┌─ ${index + 1}. ${item.title}`);
85
- console.log(` │`);
86
- console.log(` │ 📅 ${date}`);
87
- console.log(` │`);
88
- if (item.contentSnippet) {
89
- const snippet = item.contentSnippet.substring(0, 100).replace(/\n/g, ' ');
90
- console.log(` │ ${snippet}...`);
126
+
127
+ if (!feed.items || feed.items.length === 0) {
128
+ console.error(`No items found in feed from ${source.name}`);
129
+ process.exit(1);
130
+ }
131
+
132
+ return feed.items.slice(0, count);
133
+ } catch (err) {
134
+ console.error('Error fetching news:', err.message);
135
+ console.error(`URL: ${source.url}`);
136
+ process.exit(1);
137
+ }
138
+ }
139
+
140
+ function wrapText(text, width) {
141
+ const lines = [];
142
+ let start = 0;
143
+ while (start < text.length) {
144
+ let end = Math.min(start + width, text.length);
145
+ if (end < text.length && end > start) {
146
+ const lastSpace = text.lastIndexOf(' ', end);
147
+ if (lastSpace > start && lastSpace > end - width / 2) {
148
+ end = lastSpace;
91
149
  }
92
- console.log(` └─────────────────────────────────────────────────────────────────────`);
93
- console.log(` 🔗 ${item.link}\n`);
94
- });
150
+ }
151
+ lines.push(text.substring(start, end).trim());
152
+ start = end;
153
+ while (start < text.length && text[start] === ' ') {
154
+ start++;
155
+ }
156
+ }
157
+ return lines.length ? lines : [''];
158
+ }
159
+
160
+ function createScreen() {
161
+ return blessed.screen({
162
+ smartCSR: true,
163
+ title: 'Sondakika Haberler'
164
+ });
165
+ }
166
+
167
+ function createMainList(screen, items, sourceName) {
168
+ const list = blessed.list({
169
+ parent: screen,
170
+ top: 2,
171
+ left: 0,
172
+ width: '100%',
173
+ height: '100%-4',
174
+ tags: true,
175
+ keys: false,
176
+ mouse: false,
177
+ border: null,
178
+ style: {
179
+ selected: {
180
+ fg: 'white',
181
+ bg: 'blue'
182
+ }
183
+ },
184
+ scrollbar: {
185
+ ch: '█',
186
+ inverse: true
187
+ }
188
+ });
189
+
190
+ list.focus();
191
+
192
+ const header = blessed.box({
193
+ parent: screen,
194
+ top: 0,
195
+ left: 0,
196
+ width: '100%',
197
+ height: 2,
198
+ content: `{bold}📰 ${sourceName}{/bold} - {cyan-fg}▲/▼{/cyan-fg} Seç, {cyan-fg}◄/►{/cyan-fg} Sayfa, {cyan-fg}Enter{/cyan-fg} Görüntüle, {cyan-fg}Q{/cyan-fg} Çıkış`,
199
+ tags: true,
200
+ style: {
201
+ fg: 'white',
202
+ bg: 'blue'
203
+ }
204
+ });
205
+
206
+ const footer = blessed.box({
207
+ parent: screen,
208
+ bottom: 0,
209
+ left: 0,
210
+ width: '100%',
211
+ height: 2,
212
+ content: '{cyan-fg}▲{/cyan-fg} Yukarı {cyan-fg}▼{/cyan-fg} Aşağı {cyan-fg}◄{/cyan-fg} Önceki Sayfa {cyan-fg}►{/cyan-fg} Sonraki Sayfa {cyan-fg}Enter{/cyan-fg} Görüntüle {cyan-fg}Q{/cyan-fg} Çıkış',
213
+ tags: true,
214
+ style: {
215
+ fg: 'white',
216
+ bg: 'black'
217
+ }
218
+ });
219
+
220
+ return { list, header, footer };
221
+ }
222
+
223
+ function createFullScreenView(screen) {
224
+ const overlay = blessed.box({
225
+ parent: screen,
226
+ top: 0,
227
+ left: 0,
228
+ width: '100%',
229
+ height: '100%',
230
+ tags: true,
231
+ border: {
232
+ type: 'line'
233
+ },
234
+ style: {
235
+ border: {
236
+ fg: 'cyan'
237
+ }
238
+ },
239
+ hidden: true,
240
+ scrollable: true,
241
+ alwaysScroll: true,
242
+ scrolloffset: 1
243
+ });
244
+
245
+ const closeHint = blessed.box({
246
+ parent: overlay,
247
+ bottom: 0,
248
+ left: 0,
249
+ width: '100%',
250
+ height: 1,
251
+ content: '{center}Enter ile listeye dön{/center}',
252
+ tags: true,
253
+ style: {
254
+ fg: 'black',
255
+ bg: 'cyan'
256
+ }
257
+ });
258
+
259
+ return { overlay, closeHint };
260
+ }
261
+
262
+ function updateListDisplay(list, items, page, totalPages) {
263
+ const start = page * ITEMS_PER_PAGE;
264
+ const end = Math.min(start + ITEMS_PER_PAGE, items.length);
265
+ const pageItems = items.slice(start, end);
266
+
267
+ const lines = pageItems.map((item, idx) => {
268
+ const globalIdx = start + idx + 1;
269
+ const dateStr = formatDate(item.pubDate || item.isodate);
270
+ const titleLines = wrapText(item.title, 60);
271
+ let line = `{bold}${globalIdx}.{/bold} ${titleLines[0]}`;
272
+ if (dateStr) {
273
+ line += ` {gray-fg}[${dateStr}]{/gray-fg}`;
274
+ }
275
+ return line;
276
+ });
277
+
278
+ list.setItems(lines);
279
+ list.scrollTo(0);
280
+ }
281
+
282
+ function showFullScreen(overlay, item) {
283
+ const title = item.title || 'Başlık yok';
284
+ const dateStr = formatDate(item.pubDate || item.isodate);
285
+ const summary = item.summary || item.contentSnippet || item.content || 'İçerik yok';
286
+ const link = item.link || '';
287
+
288
+ const titleLines = wrapText(title, 70);
289
+ const summaryLines = wrapText(summary, 70);
290
+
291
+ let content = '{bold}' + titleLines.join('\n') + '{/bold}\n\n';
292
+
293
+ if (dateStr) {
294
+ content += `{gray-fg}📅 ${dateStr}{/gray-fg}\n\n`;
295
+ }
296
+
297
+ content += summaryLines.join('\n') + '\n\n';
298
+
299
+ if (link) {
300
+ content += `{cyan-fg}🔗 ${link}{/cyan-fg}`;
301
+ }
302
+
303
+ overlay.setContent(content);
304
+ overlay.show();
305
+ }
95
306
 
96
- } catch (error) {
97
- console.error('Haberler alınırken hata oluştu:', error.message);
307
+ function hideFullScreen(overlay) {
308
+ overlay.hide();
309
+ }
310
+
311
+ async function main() {
312
+ const args = process.argv.slice(2);
313
+ const cliState = loadCLIState();
314
+
315
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
316
+ const breakingSrc = Object.entries(sources)
317
+ .filter(([k, v]) => v.isSondakika)
318
+ .map(([k, v]) => ` ${k.padEnd(12)} ${v.name}`)
319
+ .join('\n');
320
+ const generalSrc = Object.entries(sources)
321
+ .filter(([k, v]) => !v.isSondakika)
322
+ .map(([k, v]) => ` ${k.padEnd(12)} ${v.name}`)
323
+ .join('\n');
324
+ console.log(`
325
+ 📰 Sondakika v${version} - Türkçe Haber Okuyucu (CLI)
326
+ Tam özellikli terminal haber okuyucu
327
+
328
+ Kullanım:
329
+ sondakika <kaynak> [adet]
330
+ sondakika --version
331
+ sondakika --help
332
+
333
+ Kaynaklar (Son Dakika):
334
+ ${breakingSrc}
335
+
336
+ Kaynaklar (Haberler):
337
+ ${generalSrc}
338
+
339
+ Örnekler:
340
+ sondakika cumhuriyet # Cumhuriyet haberleri
341
+ sondakika trt 15 # TRT 15 haber
342
+ sondakika mynet 5 # Mynet 5 haber
343
+ sondakika sabah # Sabah haberleri
344
+ sondakika haberturk 5 # Habertürk 5 haber
345
+ sondakika cnnturk # CNN Türk haberleri
346
+ `);
347
+ process.exit(0);
348
+ }
349
+
350
+ if (args[0] === '--version' || args[0] === '-v') {
351
+ console.log(`sondakika v${version}`);
352
+ process.exit(0);
353
+ }
354
+
355
+ const sourceKey = args[0] ? args[0].toLowerCase() : cliState.lastSource;
356
+ const count = args[1] ? parseInt(args[1]) : 1000; // Default to max (fetch all available news)
357
+
358
+ const source = sources[sourceKey];
359
+ if (!source) {
360
+ console.error(`Unknown source: ${sourceKey}`);
361
+ console.log(`Available sources: ${Object.keys(sources).join(', ')}`);
98
362
  process.exit(1);
99
363
  }
364
+
365
+ // Save the source and count for next time
366
+ cliState.lastSource = sourceKey;
367
+ cliState.itemsPerPage = count;
368
+ saveCLIState(cliState);
369
+
370
+ const sourceName = source.isSondakika ? `${source.name} (Son Dakika)` : source.name;
371
+
372
+ const screen = createScreen();
373
+ const { list, header, footer } = createMainList(screen, [], sourceName);
374
+ const { overlay, closeHint } = createFullScreenView(screen);
375
+
376
+ let items = [];
377
+ let currentPage = 0;
378
+ let totalPages = 0;
379
+ let inFullScreen = false;
380
+
381
+ const loadNews = async () => {
382
+ header.setContent(`{bold}📰 ${sourceName}{/bold} - Yükleniyor...`);
383
+ screen.render();
384
+
385
+ items = await fetchNews(sourceKey, count);
386
+ totalPages = Math.ceil(items.length / ITEMS_PER_PAGE);
387
+ currentPage = 0;
388
+
389
+ updateListDisplay(list, items, currentPage, totalPages);
390
+ list.select(0);
391
+ header.setContent(`{bold}📰 ${sourceName}{/bold} - {cyan-fg}▲/▼{/cyan-fg} Seç, {cyan-fg}◄/►{/cyan-fg} Sayfa, {cyan-fg}Enter{/cyan-fg} Görüntüle, {cyan-fg}Q{/cyan-fg} Çıkış`);
392
+ footer.setContent(`{cyan-fg}▲{/cyan-fg} Yukarı {cyan-fg}▼{/cyan-fg} Aşağı {cyan-fg}◄{/cyan-fg} Önceki Sayfa (${currentPage + 1}/${totalPages}) {cyan-fg}►{/cyan-fg} Sonraki Sayfa {cyan-fg}Enter{/cyan-fg} Görüntüle {cyan-fg}Q{/cyan-fg} Çıkış`);
393
+ screen.render();
394
+ };
395
+
396
+ const updateFooter = () => {
397
+ footer.setContent(`{cyan-fg}▲{/cyan-fg} Yukarı {cyan-fg}▼{/cyan-fg} Aşağı {cyan-fg}◄{/cyan-fg} Önceki Sayfa (${currentPage + 1}/${totalPages}) {cyan-fg}►{/cyan-fg} Sonraki Sayfa {cyan-fg}Enter{/cyan-fg} Görüntüle {cyan-fg}Q{/cyan-fg} Çıkış`);
398
+ };
399
+
400
+ const goToPrevPage = () => {
401
+ if (currentPage > 0) {
402
+ currentPage--;
403
+ updateListDisplay(list, items, currentPage, totalPages);
404
+ list.select(0);
405
+ updateFooter();
406
+ screen.render();
407
+ }
408
+ };
409
+
410
+ const goToNextPage = () => {
411
+ if (currentPage < totalPages - 1) {
412
+ currentPage++;
413
+ updateListDisplay(list, items, currentPage, totalPages);
414
+ list.select(0);
415
+ updateFooter();
416
+ screen.render();
417
+ }
418
+ };
419
+
420
+ const openFullScreen = () => {
421
+ const start = currentPage * ITEMS_PER_PAGE;
422
+ const itemIndex = start + list.selected;
423
+ if (itemIndex < items.length) {
424
+ showFullScreen(overlay, items[itemIndex]);
425
+ inFullScreen = true;
426
+ screen.render();
427
+ }
428
+ };
429
+
430
+ const closeFullScreen = () => {
431
+ hideFullScreen(overlay);
432
+ inFullScreen = false;
433
+ screen.render();
434
+ };
435
+
436
+ screen.key(['up'], () => {
437
+ if (!inFullScreen) {
438
+ list.up();
439
+ screen.render();
440
+ }
441
+ });
442
+
443
+ screen.key(['down'], () => {
444
+ if (!inFullScreen) {
445
+ list.down();
446
+ screen.render();
447
+ }
448
+ });
449
+
450
+ screen.key(['left'], () => {
451
+ if (!inFullScreen) {
452
+ goToPrevPage();
453
+ }
454
+ });
455
+
456
+ screen.key(['right'], () => {
457
+ if (!inFullScreen) {
458
+ goToNextPage();
459
+ }
460
+ });
461
+
462
+ screen.key(['enter'], () => {
463
+ if (inFullScreen) {
464
+ closeFullScreen();
465
+ } else {
466
+ openFullScreen();
467
+ }
468
+ });
469
+
470
+ // Use Escape, Q, or Ctrl+C to quit
471
+ screen.unkey(['q', 'C-c']);
472
+ screen.key(['escape', 'q', 'Q', 'C-c'], () => {
473
+ process.exit(0);
474
+ });
475
+
476
+ list.on('select', (el, idx) => {
477
+ openFullScreen();
478
+ });
479
+
480
+ await loadNews();
481
+
482
+ screen.render();
100
483
  }
101
484
 
102
- fetchNews();
485
+ main().catch(err => {
486
+ console.error('Error:', err.message);
487
+ process.exit(1);
488
+ });