sondakika 2.0.1 → 2.0.2

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.
@@ -1,1258 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="tr">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;">
7
- <title>Sondakika</title>
8
- <link rel="preconnect" href="https://fonts.googleapis.com">
9
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
11
- <style>
12
- * { margin:0; padding:0; box-sizing: border-box; }
13
-
14
- :root {
15
- --bg-primary: #0f0f1a;
16
- --bg-secondary: #1a1a2e;
17
- --bg-card: #252542;
18
- --bg-elevated: #2d2d4a;
19
- --accent-primary: #6366f1;
20
- --accent-secondary: #818cf8;
21
- --accent-hover: #4f46e5;
22
- --text-primary: #f1f5f9;
23
- --text-secondary: #94a3b8;
24
- --text-muted: #64748b;
25
- --border-color: #334155;
26
- --success: #10b981;
27
- --warning: #f59e0b;
28
- --error: #ef4444;
29
- --breaking: #ef4444;
30
- --selected-bg: rgba(99, 102, 241, 0.25);
31
- --selected-border: #6366f1;
32
- }
33
-
34
- .theme-light {
35
- --bg-primary: #f8fafc;
36
- --bg-secondary: #ffffff;
37
- --bg-card: #f1f5f9;
38
- --bg-elevated: #e2e8f0;
39
- --accent-primary: #6366f1;
40
- --accent-secondary: #818cf8;
41
- --accent-hover: #4f46e5;
42
- --text-primary: #0f172a;
43
- --text-secondary: #475569;
44
- --text-muted: #94a3b8;
45
- --border-color: #e2e8f0;
46
- --success: #10b981;
47
- --warning: #f59e0b;
48
- --error: #ef4444;
49
- --breaking: #dc2626;
50
- --selected-bg: rgba(99, 102, 241, 0.15);
51
- --selected-border: #6366f1;
52
- }
53
-
54
- body {
55
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
56
- background: var(--bg-primary);
57
- color: var(--text-primary);
58
- min-height: 100vh;
59
- overflow-x: hidden;
60
- }
61
-
62
- .app-container {
63
- display: flex;
64
- min-height: 100vh;
65
- }
66
-
67
- .sidebar {
68
- width: 320px;
69
- background: var(--bg-secondary);
70
- border-right: 1px solid var(--border-color);
71
- display: flex;
72
- flex-direction: column;
73
- position: fixed;
74
- height: 100vh;
75
- overflow-y: auto;
76
- }
77
-
78
- .sidebar-header {
79
- padding: 24px 20px;
80
- border-bottom: 1px solid var(--border-color);
81
- }
82
-
83
- .logo {
84
- display: flex;
85
- align-items: center;
86
- gap: 12px;
87
- }
88
-
89
- .logo-icon {
90
- width: 42px;
91
- height: 42px;
92
- background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
93
- border-radius: 12px;
94
- display: flex;
95
- align-items: center;
96
- justify-content: center;
97
- font-size: 22px;
98
- }
99
-
100
- .logo-text {
101
- font-size: 20px;
102
- font-weight: 700;
103
- background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
104
- -webkit-background-clip: text;
105
- -webkit-text-fill-color: transparent;
106
- }
107
-
108
- .sidebar-section {
109
- padding: 20px;
110
- border-bottom: 1px solid var(--border-color);
111
- }
112
-
113
- .section-title {
114
- font-size: 11px;
115
- font-weight: 600;
116
- text-transform: uppercase;
117
- letter-spacing: 1.2px;
118
- color: var(--text-muted);
119
- margin-bottom: 16px;
120
- }
121
-
122
- .sources-list {
123
- display: flex;
124
- flex-direction: column;
125
- gap: 8px;
126
- }
127
-
128
- .source-item {
129
- display: flex;
130
- align-items: center;
131
- gap: 12px;
132
- padding: 12px 14px;
133
- background: var(--bg-card);
134
- border-radius: 10px;
135
- cursor: pointer;
136
- transition: all 0.2s ease;
137
- border: 1px solid transparent;
138
- }
139
-
140
- .source-item:hover {
141
- background: var(--bg-elevated);
142
- border-color: var(--border-color);
143
- }
144
-
145
- .source-item.selected {
146
- background: rgba(99, 102, 241, 0.15);
147
- border-color: var(--accent-primary);
148
- }
149
-
150
- .source-item input[type="checkbox"] {
151
- width: 18px;
152
- height: 18px;
153
- border-radius: 5px;
154
- border: 2px solid var(--border-color);
155
- background: transparent;
156
- cursor: pointer;
157
- appearance: none;
158
- display: flex;
159
- align-items: center;
160
- justify-content: center;
161
- transition: all 0.2s ease;
162
- flex-shrink: 0;
163
- }
164
-
165
- .source-item input[type="checkbox"]:checked {
166
- background: var(--accent-primary);
167
- border-color: var(--accent-primary);
168
- }
169
-
170
- .source-item input[type="checkbox"]:checked::after {
171
- content: '✓';
172
- color: white;
173
- font-size: 12px;
174
- font-weight: bold;
175
- }
176
-
177
- .source-name {
178
- font-size: 14px;
179
- font-weight: 500;
180
- }
181
-
182
- .source-badge {
183
- margin-left: auto;
184
- padding: 3px 8px;
185
- background: rgba(239, 68, 68, 0.15);
186
- color: var(--breaking);
187
- font-size: 10px;
188
- font-weight: 600;
189
- border-radius: 4px;
190
- text-transform: uppercase;
191
- }
192
-
193
- .options-grid {
194
- display: grid;
195
- gap: 12px;
196
- }
197
-
198
- .option-item {
199
- display: flex;
200
- align-items: center;
201
- justify-content: space-between;
202
- padding: 14px 16px;
203
- background: var(--bg-card);
204
- border-radius: 10px;
205
- border: 1px solid transparent;
206
- transition: all 0.2s ease;
207
- }
208
-
209
- .option-item:hover {
210
- border-color: var(--border-color);
211
- }
212
-
213
- .option-label {
214
- font-size: 14px;
215
- font-weight: 500;
216
- }
217
-
218
- .option-select {
219
- background: var(--bg-elevated);
220
- border: 1px solid var(--border-color);
221
- color: var(--text-primary);
222
- padding: 8px 14px;
223
- border-radius: 8px;
224
- font-size: 13px;
225
- font-family: inherit;
226
- cursor: pointer;
227
- outline: none;
228
- }
229
-
230
- .option-select:focus {
231
- border-color: var(--accent-primary);
232
- }
233
-
234
- .font-size-controls {
235
- display: flex;
236
- align-items: center;
237
- gap: 8px;
238
- }
239
-
240
- .font-btn {
241
- width: 32px;
242
- height: 32px;
243
- background: var(--bg-elevated);
244
- border: 1px solid var(--border-color);
245
- border-radius: 6px;
246
- color: var(--text-primary);
247
- font-size: 12px;
248
- font-weight: 600;
249
- cursor: pointer;
250
- transition: all 0.2s ease;
251
- }
252
-
253
- .font-btn:hover {
254
- background: var(--accent-primary);
255
- border-color: var(--accent-primary);
256
- }
257
-
258
- .font-size-value {
259
- font-size: 14px;
260
- font-weight: 600;
261
- min-width: 40px;
262
- text-align: center;
263
- }
264
-
265
- .main-content {
266
- flex: 1;
267
- margin-left: 320px;
268
- padding: 32px 40px;
269
- min-height: 100vh;
270
- overflow-y: auto;
271
- max-height: 100vh;
272
- }
273
-
274
- .content-header {
275
- display: flex;
276
- align-items: center;
277
- justify-content: space-between;
278
- margin-bottom: 32px;
279
- position: sticky;
280
- top: 0;
281
- background: var(--bg-primary);
282
- padding: 32px 0 24px 0;
283
- z-index: 50;
284
- }
285
-
286
- .header-left {
287
- display: flex;
288
- flex-direction: column;
289
- gap: 6px;
290
- }
291
-
292
- .page-title {
293
- font-size: 28px;
294
- font-weight: 700;
295
- }
296
-
297
- .page-subtitle {
298
- font-size: 14px;
299
- color: var(--text-secondary);
300
- }
301
-
302
- .header-actions {
303
- display: flex;
304
- align-items: center;
305
- gap: 16px;
306
- flex-wrap: wrap;
307
- justify-content: flex-end;
308
- }
309
-
310
- .refresh-btn {
311
- display: flex;
312
- align-items: center;
313
- gap: 8px;
314
- padding: 12px 20px;
315
- background: var(--accent-primary);
316
- color: white;
317
- border: none;
318
- border-radius: 10px;
319
- font-size: 14px;
320
- font-weight: 600;
321
- cursor: pointer;
322
- transition: all 0.2s ease;
323
- font-family: inherit;
324
- }
325
-
326
- .refresh-btn:hover {
327
- background: var(--accent-hover);
328
- transform: translateY(-1px);
329
- }
330
-
331
- .refresh-btn:disabled {
332
- opacity: 0.6;
333
- cursor: not-allowed;
334
- transform: none;
335
- }
336
-
337
- .status-badge {
338
- display: flex;
339
- align-items: center;
340
- gap: 8px;
341
- padding: 10px 16px;
342
- background: var(--bg-card);
343
- border-radius: 8px;
344
- font-size: 13px;
345
- }
346
-
347
- .status-dot {
348
- width: 8px;
349
- height: 8px;
350
- border-radius: 50%;
351
- background: var(--success);
352
- }
353
-
354
- .status-dot.loading {
355
- background: var(--warning);
356
- animation: pulse 1s infinite;
357
- }
358
-
359
- @keyframes pulse {
360
- 0%, 100% { opacity: 1; }
361
- 50% { opacity: 0.4; }
362
- }
363
-
364
- .news-grid {
365
- display: grid;
366
- gap: 20px;
367
- }
368
-
369
- .news-card {
370
- background: var(--bg-card);
371
- border-radius: 16px;
372
- padding: 24px;
373
- border: 2px solid var(--border-color);
374
- transition: all 0.25s ease;
375
- cursor: pointer;
376
- }
377
-
378
- .news-card:hover {
379
- transform: translateY(-4px);
380
- border-color: var(--accent-primary);
381
- box-shadow: 0 12px 40px rgba(99, 102, 241, 0.15);
382
- }
383
-
384
- .news-card.selected {
385
- background: var(--selected-bg);
386
- border-color: var(--selected-border);
387
- box-shadow: 0 0 20px rgba(99, 102, 241, 0.3);
388
- }
389
-
390
- .news-card-header {
391
- display: flex;
392
- align-items: center;
393
- justify-content: space-between;
394
- margin-bottom: 14px;
395
- }
396
-
397
- .news-source {
398
- display: flex;
399
- align-items: center;
400
- gap: 10px;
401
- }
402
-
403
- .source-tag {
404
- padding: 5px 12px;
405
- background: var(--bg-elevated);
406
- border-radius: 6px;
407
- font-size: 12px;
408
- font-weight: 600;
409
- color: var(--text-secondary);
410
- }
411
-
412
- .source-tag.breaking {
413
- background: rgba(239, 68, 68, 0.15);
414
- color: var(--breaking);
415
- }
416
-
417
- .news-date-badge {
418
- background: rgba(99, 102, 241, 0.15);
419
- color: var(--accent-secondary);
420
- padding: 6px 12px;
421
- border-radius: 8px;
422
- font-weight: 600;
423
- font-size: 12px;
424
- display: flex;
425
- align-items: center;
426
- gap: 6px;
427
- }
428
-
429
- .external-link-btn {
430
- display: flex;
431
- align-items: center;
432
- gap: 6px;
433
- padding: 8px 14px;
434
- background: var(--bg-elevated);
435
- border: 1px solid var(--border-color);
436
- border-radius: 8px;
437
- color: var(--text-secondary);
438
- font-size: 12px;
439
- font-weight: 500;
440
- cursor: pointer;
441
- transition: all 0.2s ease;
442
- text-decoration: none;
443
- }
444
-
445
- .external-link-btn:hover {
446
- background: var(--accent-primary);
447
- color: white;
448
- border-color: var(--accent-primary);
449
- }
450
-
451
- .news-title {
452
- font-size: 17px;
453
- font-weight: 600;
454
- line-height: 1.5;
455
- margin-bottom: 12px;
456
- color: var(--text-primary);
457
- }
458
-
459
- .news-summary {
460
- font-size: 14px;
461
- line-height: 1.7;
462
- color: var(--text-secondary);
463
- display: -webkit-box;
464
- -webkit-line-clamp: 3;
465
- -webkit-box-orient: vertical;
466
- overflow: hidden;
467
- }
468
-
469
- .news-card-footer {
470
- display: flex;
471
- align-items: center;
472
- justify-content: space-between;
473
- margin-top: 18px;
474
- padding-top: 16px;
475
- border-top: 1px solid var(--border-color);
476
- }
477
-
478
- .read-more {
479
- font-size: 13px;
480
- font-weight: 600;
481
- color: var(--accent-primary);
482
- display: flex;
483
- align-items: center;
484
- gap: 6px;
485
- }
486
-
487
- .pagination {
488
- display: flex;
489
- align-items: center;
490
- justify-content: center;
491
- gap: 10px;
492
- margin-top: 40px;
493
- }
494
-
495
- .page-btn {
496
- display: flex;
497
- align-items: center;
498
- justify-content: center;
499
- width: 44px;
500
- height: 44px;
501
- background: var(--bg-card);
502
- border: 1px solid var(--border-color);
503
- border-radius: 10px;
504
- color: var(--text-primary);
505
- font-size: 14px;
506
- font-weight: 600;
507
- cursor: pointer;
508
- transition: all 0.2s ease;
509
- }
510
-
511
- .page-btn:hover:not(:disabled) {
512
- background: var(--bg-elevated);
513
- border-color: var(--accent-primary);
514
- }
515
-
516
- .page-btn:disabled {
517
- opacity: 0.4;
518
- cursor: not-allowed;
519
- }
520
-
521
- .page-btn.active {
522
- background: var(--accent-primary);
523
- border-color: var(--accent-primary);
524
- }
525
-
526
- .empty-state {
527
- display: flex;
528
- flex-direction: column;
529
- align-items: center;
530
- justify-content: center;
531
- padding: 80px 40px;
532
- text-align: center;
533
- }
534
-
535
- .empty-icon {
536
- width: 100px;
537
- height: 100px;
538
- background: var(--bg-card);
539
- border-radius: 24px;
540
- display: flex;
541
- align-items: center;
542
- justify-content: center;
543
- font-size: 48px;
544
- margin-bottom: 24px;
545
- }
546
-
547
- .empty-title {
548
- font-size: 22px;
549
- font-weight: 600;
550
- margin-bottom: 10px;
551
- }
552
-
553
- .empty-text {
554
- font-size: 15px;
555
- color: var(--text-secondary);
556
- max-width: 360px;
557
- }
558
-
559
- .loading-grid {
560
- display: grid;
561
- gap: 20px;
562
- }
563
-
564
- .skeleton {
565
- background: linear-gradient(90deg, var(--bg-card) 25%, var(--bg-elevated) 50%, var(--bg-card) 75%);
566
- background-size: 200% 100%;
567
- animation: shimmer 1.5s infinite;
568
- border-radius: 8px;
569
- }
570
-
571
- .skeleton-card {
572
- height: 180px;
573
- border-radius: 16px;
574
- }
575
-
576
- @keyframes shimmer {
577
- 0% { background-position: 200% 0; }
578
- 100% { background-position: -200% 0; }
579
- }
580
-
581
- .keys-hint {
582
- background: var(--bg-card);
583
- border: 1px solid var(--border-color);
584
- border-radius: 10px;
585
- padding: 8px 12px;
586
- font-size: 10px;
587
- color: var(--text-muted);
588
- display: flex;
589
- gap: 10px;
590
- opacity: 0.85;
591
- margin-right: 12px;
592
- }
593
-
594
- .keys-hint kbd {
595
- display: inline-block;
596
- padding: 2px 6px;
597
- background: var(--bg-elevated);
598
- border: 1px solid var(--border-color);
599
- border-radius: 4px;
600
- font-family: inherit;
601
- font-size: 10px;
602
- margin: 0 2px;
603
- }
604
-
605
- ::-webkit-scrollbar { width: 8px; }
606
- ::-webkit-scrollbar-track { background: var(--bg-secondary); }
607
- ::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 4px; }
608
- ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
609
-
610
- .hidden { display: none !important; }
611
- </style>
612
- </head>
613
- <body>
614
- <div class="app-container">
615
- <aside class="sidebar">
616
- <div class="sidebar-header">
617
- <div class="logo">
618
- <div class="logo-icon">📰</div>
619
- <span class="logo-text">Sondakika</span>
620
- </div>
621
- </div>
622
-
623
- <div class="sidebar-section">
624
- <div class="section-title">Haber Kaynakları</div>
625
- <div class="sources-list" id="sourcesList"></div>
626
- </div>
627
-
628
- <div class="sidebar-section">
629
- <div class="section-title">Seçenekler</div>
630
- <div class="options-grid">
631
- <div class="option-item">
632
- <span class="option-label">Sırala</span>
633
- <select class="option-select" id="sortOrder">
634
- <option value="desc">En Yeni (Önce)</option>
635
- <option value="asc">En Eski (Önce)</option>
636
- </select>
637
- </div>
638
- <div class="option-item">
639
- <span class="option-label">Sayfa Başına</span>
640
- <select class="option-select" id="itemsPerPage">
641
- <option value="5">5 Haber</option>
642
- <option value="10" selected>10 Haber</option>
643
- <option value="15">15 Haber</option>
644
- <option value="20">20 Haber</option>
645
- </select>
646
- </div>
647
- <div class="option-item">
648
- <span class="option-label">Başlık <kbd>1</kbd><kbd>2</kbd></span>
649
- <div class="font-size-controls">
650
- <button class="font-btn" id="titleFontDec" onclick="changeTitleFontSize(-1)">−</button>
651
- <span class="font-size-value" id="titleFontSizeValue">17px</span>
652
- <button class="font-btn" id="titleFontInc" onclick="changeTitleFontSize(1)">+</button>
653
- </div>
654
- </div>
655
- <div class="option-item">
656
- <span class="option-label">İçerik <kbd>3</kbd><kbd>4</kbd></span>
657
- <div class="font-size-controls">
658
- <button class="font-btn" id="contentFontDec" onclick="changeContentFontSize(-1)">−</button>
659
- <span class="font-size-value" id="contentFontSizeValue">14px</span>
660
- <button class="font-btn" id="contentFontInc" onclick="changeContentFontSize(1)">+</button>
661
- </div>
662
- </div>
663
- <div class="option-item">
664
- <span class="option-label">Tema</span>
665
- <select class="option-select" id="themeSelect" onchange="changeTheme(this.value)">
666
- <option value="dark">Karanlık</option>
667
- <option value="light">Açık</option>
668
- </select>
669
- </div>
670
- </div>
671
- </div>
672
- </aside>
673
-
674
- <main class="main-content">
675
- <div class="content-header">
676
- <div class="header-left">
677
- <h1 class="page-title">Haberler</h1>
678
- <p class="page-subtitle" id="newsCount">Yükleniyor...</p>
679
- </div>
680
- <div class="header-actions">
681
- <div class="keys-hint">
682
- <span><kbd>↑</kbd><kbd>↓</kbd> Seç</span>
683
- <span><kbd>←</kbd><kbd>→</kbd> Sayfa</span>
684
- <span><kbd>1</kbd><kbd>2</kbd> Başlık</span>
685
- <span><kbd>3</kbd><kbd>4</kbd> İçerik</span>
686
- <span><kbd>`</kbd> Tarayıcı</span>
687
- <span><kbd>Enter</kbd> Görüntüle</span>
688
- </div>
689
- <div class="status-badge">
690
- <span class="status-dot" id="statusDot"></span>
691
- <span id="statusText">Hazır</span>
692
- </div>
693
- <button class="refresh-btn" id="refreshBtn">🔄 Yenile</button>
694
- </div>
695
- </div>
696
-
697
- <div id="newsContainer">
698
- <div class="loading-grid hidden" id="loadingGrid"></div>
699
- <div class="news-grid hidden" id="newsGrid"></div>
700
- <div class="empty-state hidden" id="emptyState">
701
- <div class="empty-icon">📽</div>
702
- <h2 class="empty-title">Haber Kaynağı Seçin</h2>
703
- <p class="empty-text">Lütfen en az bir haber kaynağı seçiniz ve yenile butonuna basınız.</p>
704
- </div>
705
- </div>
706
-
707
- <div class="pagination hidden" id="pagination"></div>
708
- </main>
709
- </div>
710
-
711
- <script>
712
- let sources = {};
713
- let currentState = {
714
- enabledSources: [],
715
- sortOrder: 'desc',
716
- itemsPerPage: 10,
717
- currentPage: 1,
718
- fontSize: 16,
719
- theme: 'dark'
720
- };
721
- let allNews = [];
722
- let isLoading = false;
723
- let selectedIndex = -1;
724
-
725
- async function init() {
726
- try {
727
- if (!window.electronAPI) {
728
- document.getElementById('sourcesList').innerHTML = '<div style="color:var(--error);padding:12px;">Hata: electronAPI bulunamadı (preload.js yüklenemedi)</div>';
729
- document.getElementById('newsCount').textContent = 'electronAPI hatası';
730
- return;
731
- }
732
- sources = await window.electronAPI.getSources();
733
- const state = await window.electronAPI.getState();
734
- currentState = Object.assign({}, currentState, state);
735
-
736
- renderSources();
737
- setupEventListeners();
738
- applyState();
739
-
740
- if (currentState.enabledSources.length > 0) {
741
- await fetchNews();
742
- } else {
743
- showEmptyState();
744
- }
745
-
746
- setupKeyboardNavigation();
747
- } catch (err) {
748
- console.error('Init error:', err);
749
- document.getElementById('sourcesList').innerHTML = '<div style="color:var(--error);padding:12px;">Hata: ' + err.message + '</div>';
750
- }
751
- }
752
-
753
- function renderSources() {
754
- const container = document.getElementById('sourcesList');
755
- container.innerHTML = '';
756
-
757
- Object.entries(sources).forEach(function(entry) {
758
- var key = entry[0];
759
- var source = entry[1];
760
- const item = document.createElement('div');
761
- item.className = 'source-item';
762
- item.dataset.key = key;
763
-
764
- const checkbox = document.createElement('input');
765
- checkbox.type = 'checkbox';
766
- checkbox.checked = currentState.enabledSources.indexOf(key) !== -1;
767
-
768
- const name = document.createElement('span');
769
- name.className = 'source-name';
770
- name.textContent = source.name;
771
-
772
- item.appendChild(checkbox);
773
- item.appendChild(name);
774
-
775
- if (source.isSondakika) {
776
- const badge = document.createElement('span');
777
- badge.className = 'source-badge';
778
- badge.textContent = 'SON DAKİKA';
779
- item.appendChild(badge);
780
- }
781
-
782
- container.appendChild(item);
783
- });
784
- }
785
-
786
- function setupEventListeners() {
787
- document.getElementById('sourcesList').addEventListener('change', async function(e) {
788
- if (e.target.type === 'checkbox') {
789
- const key = e.target.closest('.source-item').dataset.key;
790
- if (e.target.checked) {
791
- if (currentState.enabledSources.indexOf(key) === -1) {
792
- currentState.enabledSources.push(key);
793
- }
794
- } else {
795
- currentState.enabledSources = currentState.enabledSources.filter(function(s) { return s !== key; });
796
- }
797
- await saveState();
798
- if (currentState.enabledSources.length > 0) {
799
- await fetchNews();
800
- } else {
801
- showEmptyState();
802
- }
803
- }
804
- });
805
-
806
- document.getElementById('sortOrder').addEventListener('change', async function(e) {
807
- currentState.sortOrder = e.target.value;
808
- await saveState();
809
- if (allNews.length > 0) {
810
- await fetchNews();
811
- }
812
- });
813
-
814
- document.getElementById('itemsPerPage').addEventListener('change', async function(e) {
815
- currentState.itemsPerPage = parseInt(e.target.value);
816
- currentState.currentPage = 1;
817
- await saveState();
818
- renderNews();
819
- });
820
-
821
- document.getElementById('refreshBtn').addEventListener('click', async function() {
822
- await fetchNews();
823
- });
824
- }
825
-
826
- function setupKeyboardNavigation() {
827
- document.addEventListener('keydown', function(e) {
828
- if (allNews.length === 0) return;
829
-
830
- var totalPages = Math.ceil(allNews.length / currentState.itemsPerPage);
831
- var startIdx = (currentState.currentPage - 1) * currentState.itemsPerPage;
832
- var endIdx = Math.min(startIdx + currentState.itemsPerPage, allNews.length);
833
- var itemsOnPage = endIdx - startIdx;
834
-
835
- switch (e.key) {
836
- case 'ArrowUp':
837
- e.preventDefault();
838
- if (selectedIndex > 0) {
839
- selectNewsItem(selectedIndex - 1);
840
- }
841
- break;
842
-
843
- case 'ArrowDown':
844
- e.preventDefault();
845
- if (selectedIndex < itemsOnPage - 1) {
846
- selectNewsItem(selectedIndex + 1);
847
- }
848
- break;
849
-
850
- case 'ArrowLeft':
851
- e.preventDefault();
852
- if (currentState.currentPage > 1) {
853
- currentState.currentPage--;
854
- selectedIndex = 0;
855
- renderNews();
856
- saveState();
857
- }
858
- break;
859
-
860
- case 'ArrowRight':
861
- e.preventDefault();
862
- if (currentState.currentPage < totalPages) {
863
- currentState.currentPage++;
864
- selectedIndex = 0;
865
- renderNews();
866
- saveState();
867
- }
868
- break;
869
-
870
- case 'Enter':
871
- e.preventDefault();
872
- if (selectedIndex >= 0) {
873
- var globalIdx = startIdx + selectedIndex;
874
- openArticleView(globalIdx);
875
- }
876
- break;
877
-
878
- case '1':
879
- e.preventDefault();
880
- changeTitleFontSize(-1);
881
- break;
882
-
883
- case '2':
884
- e.preventDefault();
885
- changeTitleFontSize(1);
886
- break;
887
-
888
- case '3':
889
- e.preventDefault();
890
- changeContentFontSize(-1);
891
- break;
892
-
893
- case '4':
894
- e.preventDefault();
895
- changeContentFontSize(1);
896
- break;
897
-
898
- case '`':
899
- e.preventDefault();
900
- if (selectedIndex >= 0) {
901
- var globalIdx = startIdx + selectedIndex;
902
- var item = allNews[globalIdx];
903
- if (item && item.link) window.electronAPI.openExternal(item.link);
904
- }
905
- break;
906
- }
907
- });
908
- }
909
-
910
- function selectNewsItem(index) {
911
- selectedIndex = index;
912
- updateSelectedCard();
913
- }
914
-
915
- function updateSelectedCard() {
916
- var cards = document.querySelectorAll('.news-card');
917
- cards.forEach(function(card, idx) {
918
- if (idx === selectedIndex) {
919
- card.classList.add('selected');
920
- card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
921
- } else {
922
- card.classList.remove('selected');
923
- }
924
- });
925
- }
926
-
927
- function applyState() {
928
- var el;
929
- el = document.getElementById('sortOrder'); if (el) el.value = currentState.sortOrder;
930
- el = document.getElementById('itemsPerPage'); if (el) el.value = currentState.itemsPerPage;
931
- el = document.getElementById('titleFontSizeValue'); if (el) el.textContent = (currentState.titleFontSize || 17) + 'px';
932
- el = document.getElementById('contentFontSizeValue'); if (el) el.textContent = (currentState.contentFontSize || 14) + 'px';
933
- el = document.getElementById('themeSelect'); if (el) el.value = currentState.theme || 'dark';
934
- applyFontSize(currentState.fontSize);
935
- applyTitleFontSize(currentState.titleFontSize || 17);
936
- applyContentFontSize(currentState.contentFontSize || 14);
937
- applyTheme(currentState.theme || 'dark');
938
- }
939
-
940
- async function saveState() {
941
- await window.electronAPI.saveState(currentState);
942
- }
943
-
944
- function applyFontSize(size) {
945
- if (document.body) document.body.style.fontSize = size + 'px';
946
- }
947
-
948
- function applyTitleFontSize(size) {
949
- var els = document.querySelectorAll('.news-title');
950
- for (var i = 0; i < els.length; i++) {
951
- els[i].style.fontSize = size + 'px';
952
- }
953
- }
954
-
955
- function applyContentFontSize(size) {
956
- var els = document.querySelectorAll('.news-summary');
957
- for (var i = 0; i < els.length; i++) {
958
- els[i].style.fontSize = size + 'px';
959
- }
960
- }
961
-
962
- // Apply saved font sizes to new items when rendering
963
- function applyFontSizesToNews() {
964
- applyTitleFontSize(currentState.titleFontSize || 17);
965
- applyContentFontSize(currentState.contentFontSize || 14);
966
- }
967
-
968
- function applyTheme(theme) {
969
- document.body.classList.remove('theme-light');
970
- if (theme === 'light') {
971
- document.body.classList.add('theme-light');
972
- }
973
- }
974
-
975
- async function changeTheme(theme) {
976
- currentState.theme = theme;
977
- applyTheme(theme);
978
- await window.electronAPI.setTheme(theme);
979
- }
980
-
981
- async function changeTitleFontSize(delta) {
982
- var newSize = (currentState.titleFontSize || 17) + delta;
983
- newSize = Math.max(12, Math.min(32, newSize));
984
- currentState.titleFontSize = newSize;
985
- var el = document.getElementById('titleFontSizeValue');
986
- if (el) el.textContent = newSize + 'px';
987
- applyTitleFontSize(newSize);
988
- await window.electronAPI.setTitleFontSize(newSize);
989
- }
990
-
991
- async function changeContentFontSize(delta) {
992
- var newSize = (currentState.contentFontSize || 14) + delta;
993
- newSize = Math.max(10, Math.min(28, newSize));
994
- currentState.contentFontSize = newSize;
995
- var el = document.getElementById('contentFontSizeValue');
996
- if (el) el.textContent = newSize + 'px';
997
- applyContentFontSize(newSize);
998
- await window.electronAPI.setContentFontSize(newSize);
999
- }
1000
-
1001
- // Apply saved font sizes to new items when rendering
1002
- function applyFontSizesToNews() {
1003
- applyTitleFontSize(currentState.titleFontSize || 17);
1004
- applyContentFontSize(currentState.contentFontSize || 14);
1005
- }
1006
-
1007
- function applyTheme(theme) {
1008
- document.body.classList.remove('theme-light');
1009
- if (theme === 'light') {
1010
- document.body.classList.add('theme-light');
1011
- }
1012
- }
1013
-
1014
- async function changeTheme(theme) {
1015
- currentState.theme = theme;
1016
- applyTheme(theme);
1017
- await window.electronAPI.setTheme(theme);
1018
- }
1019
-
1020
- async function changeTitleFontSize(delta) {
1021
- var newSize = (currentState.titleFontSize || 17) + delta;
1022
- newSize = Math.max(12, Math.min(32, newSize));
1023
- currentState.titleFontSize = newSize;
1024
- var el = document.getElementById('titleFontSizeValue');
1025
- if (el) el.textContent = newSize + 'px';
1026
- applyTitleFontSize(newSize);
1027
- await window.electronAPI.setTitleFontSize(newSize);
1028
- }
1029
-
1030
- async function changeContentFontSize(delta) {
1031
- var newSize = (currentState.contentFontSize || 14) + delta;
1032
- newSize = Math.max(10, Math.min(28, newSize));
1033
- currentState.contentFontSize = newSize;
1034
- var el = document.getElementById('contentFontSizeValue');
1035
- if (el) el.textContent = newSize + 'px';
1036
- applyContentFontSize(newSize);
1037
- await window.electronAPI.setContentFontSize(newSize);
1038
- }
1039
-
1040
- async function fetchNews() {
1041
- if (isLoading) return;
1042
-
1043
- isLoading = true;
1044
- setLoading(true);
1045
-
1046
- try {
1047
- allNews = await window.electronAPI.fetchNews(
1048
- currentState.enabledSources,
1049
- currentState.sortOrder
1050
- );
1051
- currentState.currentPage = 1;
1052
- selectedIndex = -1;
1053
- await saveState();
1054
- renderNews();
1055
- } catch (err) {
1056
- console.error('Haberler alınırken hata:', err);
1057
- } finally {
1058
- isLoading = false;
1059
- setLoading(false);
1060
- }
1061
- }
1062
-
1063
- function setLoading(loading) {
1064
- var dot = document.getElementById('statusDot');
1065
- var text = document.getElementById('statusText');
1066
- var btn = document.getElementById('refreshBtn');
1067
- var loadingGrid = document.getElementById('loadingGrid');
1068
- var newsGrid = document.getElementById('newsGrid');
1069
- var empty = document.getElementById('emptyState');
1070
-
1071
- if (loading) {
1072
- dot.classList.add('loading');
1073
- text.textContent = 'Yükleniyor...';
1074
- btn.disabled = true;
1075
- // Show loading skeletons
1076
- showLoading();
1077
- loadingGrid.classList.remove('hidden');
1078
- newsGrid.classList.add('hidden');
1079
- empty.classList.add('hidden');
1080
- } else {
1081
- dot.classList.remove('loading');
1082
- text.textContent = 'Hazır';
1083
- btn.disabled = false;
1084
- loadingGrid.classList.add('hidden');
1085
- }
1086
- }
1087
-
1088
- function renderNews() {
1089
- var grid = document.getElementById('newsGrid');
1090
- var empty = document.getElementById('emptyState');
1091
- var loading = document.getElementById('loadingGrid');
1092
- var pag = document.getElementById('pagination');
1093
-
1094
- // Always hide loading grid when renderNews is called
1095
- loading.classList.add('hidden');
1096
-
1097
- if (allNews.length === 0) {
1098
- grid.classList.add('hidden');
1099
- pag.classList.add('hidden');
1100
- empty.classList.remove('hidden');
1101
- document.getElementById('newsCount').textContent = 'Kaynak seçilmedi';
1102
- return;
1103
- }
1104
-
1105
- empty.classList.add('hidden');
1106
- grid.classList.remove('hidden');
1107
-
1108
- var start = (currentState.currentPage - 1) * currentState.itemsPerPage;
1109
- var end = start + currentState.itemsPerPage;
1110
- var pageNews = allNews.slice(start, end);
1111
- var totalPages = Math.ceil(allNews.length / currentState.itemsPerPage);
1112
-
1113
- document.getElementById('newsCount').textContent = allNews.length + ' haber • Sayfa ' + currentState.currentPage + '/' + totalPages;
1114
-
1115
- var html = '';
1116
- pageNews.forEach(function(news, index) {
1117
- var date = formatDate(news.pubDate || news.isoDate);
1118
- var summary = news.summary || news.contentSnippet || news.description || '';
1119
- summary = summary.replace(/<[^>]*>/g, '').replace(/&nbsp;/g, ' ').replace(/\s+/g, ' ').trim();
1120
- var newsIndex = start + index;
1121
-
1122
- var breakingClass = news.isSondakika ? ' breaking' : '';
1123
- var escapedTitle = escapeHtml(news.title);
1124
- var escapedSummary = escapeHtml(summary);
1125
-
1126
- html += '<article class="news-card' + (index === selectedIndex ? ' selected' : '') + '" data-index="' + newsIndex + '" onclick="window.selectAndOpen(' + newsIndex + ')">' +
1127
- '<div class="news-card-header">' +
1128
- '<div class="news-source">' +
1129
- '<span class="source-tag' + breakingClass + '">' + news.sourceName + '</span>' +
1130
- '</div>' +
1131
- '<span class="news-date-badge">🕒 ' + date + '</span>' +
1132
- '</div>' +
1133
- '<h2 class="news-title">' + escapedTitle + '</h2>' +
1134
- '<p class="news-summary">' + escapedSummary + '</p>' +
1135
- '<div class="news-card-footer">' +
1136
- '<span class="read-more" onclick="event.stopPropagation(); window.openArticleView(' + newsIndex + ')">Görüntüle</span>' +
1137
- '<span class="external-link-btn" onclick="event.stopPropagation(); window.electronAPI.openExternal(\"' + news.link + '\"")">🔗 Tarayıcıda aç</span>' +
1138
- '</div>' +
1139
- '</article>';
1140
- });
1141
-
1142
- grid.innerHTML = html;
1143
-
1144
- renderPagination(totalPages);
1145
-
1146
- var itemsOnPage = Math.min(currentState.itemsPerPage, allNews.length - start);
1147
- if (selectedIndex >= itemsOnPage) {
1148
- selectedIndex = itemsOnPage - 1;
1149
- }
1150
- if (selectedIndex >= 0) {
1151
- updateSelectedCard();
1152
- }
1153
-
1154
- // Apply saved font sizes to newly rendered news items
1155
- applyFontSizesToNews();
1156
- }
1157
-
1158
- function renderPagination(totalPages) {
1159
- var pag = document.getElementById('pagination');
1160
-
1161
- if (totalPages <= 1) {
1162
- pag.classList.add('hidden');
1163
- return;
1164
- }
1165
-
1166
- pag.classList.remove('hidden');
1167
- var html = '';
1168
-
1169
- html += '<button class="page-btn" onclick="window.changePage(1)"' + (currentState.currentPage === 1 ? ' disabled' : '') + '>«</button>';
1170
- html += '<button class="page-btn" onclick="window.changePage(' + (currentState.currentPage - 1) + ')"' + (currentState.currentPage === 1 ? ' disabled' : '') + '>‹</button>';
1171
-
1172
- for (var i = 1; i <= totalPages; i++) {
1173
- if (i === 1 || i === totalPages || (i >= currentState.currentPage - 1 && i <= currentState.currentPage + 1)) {
1174
- html += '<button class="page-btn' + (i === currentState.currentPage ? ' active' : '') + '" onclick="window.changePage(' + i + ')">' + i + '</button>';
1175
- } else if (i === currentState.currentPage - 2 || i === currentState.currentPage + 2) {
1176
- html += '<span style="color: var(--text-muted);">...</span>';
1177
- }
1178
- }
1179
-
1180
- html += '<button class="page-btn" onclick="window.changePage(' + (currentState.currentPage + 1) + ')"' + (currentState.currentPage === totalPages ? ' disabled' : '') + '>›</button>';
1181
- html += '<button class="page-btn" onclick="window.changePage(' + totalPages + ')"' + (currentState.currentPage === totalPages ? ' disabled' : '') + '>»</button>';
1182
-
1183
- pag.innerHTML = html;
1184
- }
1185
-
1186
- window.changePage = async function(page) {
1187
- currentState.currentPage = page;
1188
- selectedIndex = 0;
1189
- renderNews();
1190
- await saveState();
1191
- };
1192
-
1193
- function formatDate(dateStr) {
1194
- if (!dateStr) return '';
1195
- var date = new Date(dateStr);
1196
- if (isNaN(date.getTime())) return '';
1197
-
1198
- return date.toLocaleDateString('tr-TR', {
1199
- day: '2-digit',
1200
- month: '2-digit',
1201
- year: 'numeric',
1202
- hour: '2-digit',
1203
- minute: '2-digit'
1204
- });
1205
- }
1206
-
1207
- function escapeHtml(text) {
1208
- if (!text) return '';
1209
- var div = document.createElement('div');
1210
- div.textContent = text;
1211
- return div.innerHTML;
1212
- }
1213
-
1214
- function showEmptyState() {
1215
- document.getElementById('loadingGrid').classList.add('hidden');
1216
- document.getElementById('newsGrid').classList.add('hidden');
1217
- document.getElementById('pagination').classList.add('hidden');
1218
- document.getElementById('emptyState').classList.remove('hidden');
1219
- document.getElementById('newsCount').textContent = 'Kaynak seçilmedi';
1220
- }
1221
-
1222
- function showLoading() {
1223
- var loading = document.getElementById('loadingGrid');
1224
- loading.classList.remove('hidden');
1225
- var skeletons = '';
1226
- for (var i = 0; i < 5; i++) {
1227
- skeletons += '<div class="skeleton skeleton-card"></div>';
1228
- }
1229
- loading.innerHTML = skeletons;
1230
- }
1231
-
1232
- window.selectAndOpen = function(index) {
1233
- selectedIndex = index - (currentState.currentPage - 1) * currentState.itemsPerPage;
1234
- openArticleView(index);
1235
- };
1236
-
1237
- window.openArticleView = function(newsIndex) {
1238
- if (newsIndex < 0 || newsIndex >= allNews.length) return;
1239
- window.electronAPI.openArticleView(allNews, newsIndex);
1240
- };
1241
-
1242
- window.electronAPI.onArticleViewClosed(function(returnedIndex) {
1243
- if (returnedIndex >= 0 && allNews && allNews[returnedIndex]) {
1244
- var newPage = Math.floor(returnedIndex / currentState.itemsPerPage) + 1;
1245
- if (newPage !== currentState.currentPage) {
1246
- currentState.currentPage = newPage;
1247
- renderNews();
1248
- }
1249
- selectedIndex = returnedIndex - (currentState.currentPage - 1) * currentState.itemsPerPage;
1250
- updateSelectedCard();
1251
- }
1252
- });
1253
-
1254
- // Don't call showLoading() here - it will be called by setLoading(true) when fetchNews runs
1255
- init();
1256
- </script>
1257
- </body>
1258
- </html>