@artilingo/artiframe-cli 2.0.4 → 2.0.5

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.
Files changed (90) hide show
  1. package/LICENSE +660 -660
  2. package/bin/artiframe.js +0 -0
  3. package/bin/artiframe.php +0 -0
  4. package/core-stubs/app/ApiAuthControl.php +76 -0
  5. package/core-stubs/app/ApiControl.php +81 -35
  6. package/core-stubs/app/AuthControl.php +39 -0
  7. package/core-stubs/app/Database.php +36 -35
  8. package/core-stubs/app/DotEnv.php +19 -1
  9. package/core-stubs/app/ViewControl.php +47 -1
  10. package/core-stubs/bin/SQLMethod.php +261 -0
  11. package/core-stubs/bin/SystemMethod.php +115 -74
  12. package/core-stubs/bin/ViewMethod.php +19 -5
  13. package/core-stubs/docs/de.html +4325 -4347
  14. package/core-stubs/docs/en.html +4325 -4347
  15. package/core-stubs/docs/es.html +4325 -4347
  16. package/core-stubs/docs/fr.html +4325 -4347
  17. package/core-stubs/docs/tr.html +117 -46
  18. package/core-stubs/public/403.php +52 -0
  19. package/core-stubs/public/assets/css/components/header.css +174 -0
  20. package/core-stubs/public/assets/css/components/mobilenav.css +198 -0
  21. package/core-stubs/public/assets/css/components/sidebar.css +184 -0
  22. package/core-stubs/public/assets/css/root/app.css +975 -38
  23. package/core-stubs/public/assets/css/root/root.css +64 -0
  24. package/core-stubs/public/assets/js/components/header.js +380 -0
  25. package/core-stubs/public/assets/js/components/mobilenav.js +97 -0
  26. package/core-stubs/public/assets/js/components/theme-modal.js +74 -0
  27. package/core-stubs/public/assets/js/root/app.js +1870 -0
  28. package/core-stubs/public/includes/footer.php +4 -5
  29. package/core-stubs/public/includes/head-auth.php +65 -0
  30. package/core-stubs/public/includes/head.php +56 -17
  31. package/core-stubs/public/includes/header.php +135 -1
  32. package/core-stubs/public/includes/mobilenav.php +75 -3
  33. package/core-stubs/public/includes/sidebar.php +186 -1
  34. package/core-stubs/public/includes/theme-modal.php +1 -0
  35. package/db-studio/debug.log +3 -0
  36. package/db-studio/index.html +1755 -0
  37. package/db-studio/launcher.html +102 -0
  38. package/db-studio/launcher.js +180 -0
  39. package/db-studio/logo.svg +22 -0
  40. package/db-studio/main.js +568 -0
  41. package/db-studio/package-lock.json +1034 -0
  42. package/db-studio/package.json +16 -0
  43. package/db-studio/preload.js +29 -0
  44. package/devops-studio/api-inspector.js +222 -0
  45. package/devops-studio/index.html +1892 -0
  46. package/devops-studio/logo.svg +22 -0
  47. package/devops-studio/main.js +67 -0
  48. package/devops-studio/make-wizard.js +635 -0
  49. package/devops-studio/package-lock.json +871 -0
  50. package/devops-studio/package.json +12 -0
  51. package/package.json +5 -2
  52. package/scripts/postinstall.js +44 -8
  53. package/server-studio/.json-404-guard +1 -0
  54. package/server-studio/index.html +283 -0
  55. package/server-studio/logo.svg +22 -0
  56. package/server-studio/main.js +133 -0
  57. package/server-studio/package-lock.json +1179 -0
  58. package/server-studio/package.json +15 -0
  59. package/server-studio/router.php +99 -0
  60. package/src/App.php +29 -0
  61. package/src/Commands/AddCommand.php +26 -9
  62. package/src/Commands/DbExportCommand.php +131 -0
  63. package/src/Commands/DbStartCommand.php +46 -0
  64. package/src/Commands/DevopsCommand.php +35 -0
  65. package/src/Commands/GoCommand.php +48 -48
  66. package/src/Commands/ListCommand.php +68 -68
  67. package/src/Commands/MakeApiCommand.php +6 -1
  68. package/src/Commands/MakeViewCommand.php +25 -2
  69. package/src/Commands/NewProjectCommand.php +16 -11
  70. package/src/Commands/OptimizeCommand.php +61 -0
  71. package/src/Commands/RemoveCommand.php +35 -7
  72. package/src/Commands/ServeCommand.php +46 -91
  73. package/src/Commands/ShowCommand.php +24 -24
  74. package/src/Commands/UpgradeCommand.php +39 -10
  75. package/stubs/make/api-switch-case.stub +1 -1
  76. package/stubs/make/auth-api-standart.stub +20 -0
  77. package/stubs/make/auth-api-switch-case.stub +38 -0
  78. package/stubs/make/auth-view.stub +26 -0
  79. package/stubs/make/class.stub +10 -2
  80. package/stubs/make/view.stub +3 -0
  81. package/stubs/service/image.stub +334 -334
  82. package/stubs/service/iyzico.stub +637 -637
  83. package/stubs/service/jwt.stub +312 -312
  84. package/stubs/service/phpmailer.stub +143 -143
  85. package/stubs/service/pusher.stub +232 -232
  86. package/stubs/service/qrcode.stub +169 -169
  87. package/stubs/service/redis.stub +361 -361
  88. package/stubs/service/s3.stub +377 -377
  89. package/stubs/service/stripe.stub +526 -526
  90. package/stubs/service/twilio.stub +361 -361
@@ -0,0 +1,64 @@
1
+ /* ==========================================================================
2
+ ArtiFrame Global Variables & Base Styles
3
+ (Driven by data-attributes: data-theme and data-mode)
4
+ ========================================================================== */
5
+
6
+ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Comfortaa:wght@700&display=swap');
7
+
8
+ /* Light Mode (Varsayılan) */
9
+ html[data-theme="default"][data-mode="light"] {
10
+ --brand-primary: #3B1F6E;
11
+ --brand-secondary: #1A5C38;
12
+ --brand-accent: #D4622A;
13
+
14
+ --canvas-bg: #F8F9FA;
15
+ --card-bg: #FFFFFF;
16
+ --border: #EBEBEB;
17
+
18
+ --text-main: #1C1924;
19
+ --text-muted: #645F73;
20
+
21
+ --success: #1A5C38;
22
+ --success-light: #E4F4EC;
23
+ --info: #2B75D3;
24
+ --info-light: #E8F1FC;
25
+ --warning: #E89A15;
26
+ --warning-light: #FDF5E7;
27
+ --danger: #D63946;
28
+ --danger-light: #FBEAEC;
29
+
30
+ /* Extended Vars */
31
+ --text-main-rgb: 28, 25, 36;
32
+ --brand-primary-rgb: 59, 31, 110;
33
+ --brand-secondary-rgb: 26, 92, 56;
34
+ --border-color: var(--border);
35
+ }
36
+
37
+ /* Dark Mode */
38
+ html[data-theme="default"][data-mode="dark"] {
39
+ --brand-primary: #A98CFA;
40
+ --brand-secondary: #34D399;
41
+ --brand-accent: #FF9B71;
42
+
43
+ --canvas-bg: #1B1924;
44
+ --card-bg: #252233;
45
+ --border: #3A354A;
46
+
47
+ --text-main: #F4F0FF;
48
+ --text-muted: #A09CA8;
49
+
50
+ --success: #34D399;
51
+ --success-light: rgba(52, 211, 153, 0.15);
52
+ --info: #60A5FA;
53
+ --info-light: rgba(96, 165, 250, 0.15);
54
+ --warning: #FBBF24;
55
+ --warning-light: rgba(251, 191, 36, 0.15);
56
+ --danger: #F87171;
57
+ --danger-light: rgba(248, 113, 113, 0.15);
58
+
59
+ /* Extended Vars */
60
+ --text-main-rgb: 244, 240, 255;
61
+ --brand-primary-rgb: 169, 140, 250;
62
+ --brand-secondary-rgb: 52, 211, 153;
63
+ --border-color: var(--border);
64
+ }
@@ -0,0 +1,380 @@
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ // Mobil Profil Açılır Menü Mantığı
3
+ const profileBtn = document.getElementById('profileDropdownBtn');
4
+ const profileMenu = document.getElementById('profileDropdownMenu');
5
+
6
+ if (profileBtn && profileMenu) {
7
+ // Tıklama ile menüyü aç/kapat
8
+ profileBtn.addEventListener('click', (e) => {
9
+ e.stopPropagation();
10
+ profileMenu.classList.toggle('show');
11
+ });
12
+
13
+ // Menü dışına tıklanınca kapat
14
+ document.addEventListener('click', (e) => {
15
+ if (!profileBtn.contains(e.target) && !profileMenu.contains(e.target)) {
16
+ profileMenu.classList.remove('show');
17
+ }
18
+ });
19
+ }
20
+
21
+ // Bildirim Menüsü Mantığı
22
+ const notifBtn = document.getElementById('notificationBtn');
23
+ const notifMenu = document.getElementById('notificationDropdownMenu');
24
+ const notifBadge = document.getElementById('notificationBadge');
25
+ const notifSubscriptions = document.getElementById('notif-subscriptions');
26
+ const notifGeneral = document.getElementById('notif-general');
27
+ const notifSpecial = document.getElementById('notif-special');
28
+ const notifSystem = document.getElementById('notif-system');
29
+ const markReadBtn = document.getElementById('notificationMarkReadText');
30
+ const notifTabs = document.querySelectorAll('.notif-tab');
31
+
32
+ let unreadIds = [];
33
+
34
+ const createItemHTML = (n) => {
35
+ const item = document.createElement('a');
36
+ item.className = 'dropdown-item notif-item';
37
+ item.dataset.id = n.bildirimId;
38
+ item.style.display = 'flex';
39
+ item.style.flexDirection = 'column';
40
+ item.style.alignItems = 'flex-start';
41
+ item.style.padding = '12px 15px';
42
+ item.style.whiteSpace = 'normal';
43
+ item.style.borderBottom = '1px solid var(--border)';
44
+
45
+ if (n.okundu === 0) {
46
+ item.style.background = 'rgba(139, 92, 246, 0.08)';
47
+ item.style.borderLeft = '3px solid var(--brand-primary)';
48
+ } else {
49
+ item.style.borderLeft = '3px solid transparent';
50
+ item.style.opacity = '0.7';
51
+ }
52
+
53
+ // Dinamik Yönlendirme (Deep-link) - Backend Notification.php ile uyumlu
54
+ let link = 'javascript:void(0)';
55
+ if (n.hedefTuru === 1) link = n.gonderenAdi ? `/user/profil/${n.gonderenAdi}` : `javascript:void(0)`; // PROFIL
56
+ else if (n.hedefTuru === 2) link = `/soru/${n.hedefId}`; // SORU
57
+ else if (n.hedefTuru === 3) link = `/soru/${n.hedefId}`; // COZUM -> Soru detayına git
58
+ else if (n.hedefTuru === 4) link = `/gonderi/${n.hedefId}`; // GLOBAL GONDERI
59
+ else if (n.hedefTuru === 6) link = `/groups/grup/${n.hedefId}`; // GRUP (hedefId = slug)
60
+ else if (n.hedefTuru === 8) link = `/groups/gonderi/${n.hedefId}`; // GRUP GÖNDERİSİ
61
+
62
+ item.href = link;
63
+
64
+ // Avatar Tasarımı
65
+ let avatarHtml = '';
66
+ if (n.gonderenAdi) {
67
+ let avatarUrl = "https://sinavortagim.artilingo.com/system/default-avatar.png";
68
+ if (n.profilFotografi) {
69
+ avatarUrl = n.profilFotografi.startsWith('http') ? n.profilFotografi : '/uploads/profiles/' + n.profilFotografi;
70
+ }
71
+ avatarHtml = `<img data-src="${avatarUrl}" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3C/svg%3E" style="width:36px; height:36px; border-radius:50%; object-fit:cover;" class="lazy-notif-avatar" />`;
72
+ } else {
73
+ avatarHtml = `<div style="width:36px; height:36px; border-radius:50%; background:var(--brand-primary); display:flex; align-items:center; justify-content:center; color:#fff;"><i class="fa-solid fa-bell"></i></div>`;
74
+ }
75
+
76
+ item.innerHTML = `
77
+ <div style="display:flex; gap:12px; align-items:flex-start; width:100%;">
78
+ <div style="flex-shrink:0; margin-top:3px;">
79
+ ${avatarHtml}
80
+ </div>
81
+ <div style="flex-grow:1; display:flex; flex-direction:column; overflow:hidden;">
82
+ <strong style="font-size: 0.9rem; color: var(--text-main); margin-bottom: 3px;">
83
+ ${n.okundu === 0 ? '<span style="color: var(--brand-primary); font-size: 1.2rem; line-height: 0.5;">•</span> ' : ''}${n.baslik}
84
+ </strong>
85
+ <span style="font-size: 0.8rem; color: var(--text-muted); line-height: 1.4; word-break: break-word;">${n.mesaj}</span>
86
+ <small style="font-size: 0.7rem; color: var(--brand-primary); margin-top: 5px;">
87
+ ${new Date(n.olusturulmaTarihi * 1000).toLocaleString('tr-TR')}
88
+ </small>
89
+ </div>
90
+ </div>
91
+ `;
92
+ return item;
93
+ };
94
+
95
+ const fetchNotifications = async () => {
96
+ try {
97
+ const formData = new FormData();
98
+ if (document.querySelector('meta[name="csrf-token"]')) formData.append('csrf_token', document.querySelector('meta[name="csrf-token"]').content);
99
+ formData.append('action', 'getUnread');
100
+ const response = await fetch('/api/switch-case/notification', {
101
+ method: 'POST',
102
+ body: formData
103
+ });
104
+ const result = await response.json();
105
+ if (result.status === 'success') {
106
+ const subsData = result.data.subscriptions || [];
107
+ const specialData = result.data.special || [];
108
+ const generalData = result.data.general || [];
109
+ const systemData = result.data.system || [];
110
+
111
+ unreadIds = [
112
+ ...subsData.filter(n => n.okundu === 0).map(n => n.bildirimId),
113
+ ...specialData.filter(n => n.okundu === 0).map(n => n.bildirimId),
114
+ ...generalData.filter(n => n.okundu === 0).map(n => n.bildirimId),
115
+ ...systemData.filter(n => n.okundu === 0).map(n => n.bildirimId)
116
+ ];
117
+
118
+ if (unreadIds.length > 0) {
119
+ notifBadge.innerText = '';
120
+ notifBadge.style.width = '10px';
121
+ notifBadge.style.height = '10px';
122
+ notifBadge.style.padding = '0';
123
+ notifBadge.style.minWidth = 'unset';
124
+ notifBadge.style.borderRadius = '50%';
125
+ notifBadge.style.top = '2px';
126
+ notifBadge.style.right = '4px';
127
+ notifBadge.style.display = 'block';
128
+ markReadBtn.style.display = 'block';
129
+ } else {
130
+ notifBadge.style.display = 'none';
131
+ markReadBtn.style.display = 'none';
132
+ }
133
+
134
+ const renderTab = (pane, data, tabSelector) => {
135
+ pane.innerHTML = '';
136
+
137
+ const unreadCount = data.filter(n => n.okundu === 0).length;
138
+ const tabBtn = document.querySelector(tabSelector);
139
+ if (tabBtn) {
140
+ const baseText = tabBtn.innerHTML.split(' <')[0].trim();
141
+ if (unreadCount > 0) {
142
+ tabBtn.innerHTML = `${baseText} <span class="notif-dot" style="display:inline-block; width:6px; height:6px; background:var(--brand-primary); border-radius:50%; margin-left:4px; vertical-align:middle; box-shadow: 0 0 5px var(--brand-primary);"></span>`;
143
+ } else {
144
+ tabBtn.innerHTML = baseText;
145
+ }
146
+ }
147
+
148
+ if (data.length > 0) {
149
+ data.forEach(n => pane.appendChild(createItemHTML(n)));
150
+ } else {
151
+ pane.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--text-muted); font-size: 0.85rem;">Yeni bildiriminiz yok.</div>';
152
+ }
153
+ };
154
+
155
+ renderTab(notifSubscriptions, subsData, '[data-target="notif-subscriptions"]');
156
+ renderTab(notifGeneral, generalData, '[data-target="notif-general"]');
157
+ renderTab(notifSpecial, specialData, '[data-target="notif-special"]');
158
+ renderTab(notifSystem, systemData, '[data-target="notif-system"]');
159
+ }
160
+ } catch (e) {
161
+ console.error('Bildirimler alınamadı', e);
162
+ }
163
+ };
164
+
165
+ const markAsRead = async (targetPaneId = null) => {
166
+ if (unreadIds.length === 0) return;
167
+
168
+ let pane = document;
169
+ let idsToMark = unreadIds;
170
+
171
+ if (targetPaneId !== 'all') {
172
+ if (!targetPaneId) {
173
+ const activeTab = document.querySelector('.notif-tab.active');
174
+ if (activeTab) targetPaneId = activeTab.dataset.target;
175
+ }
176
+ if (targetPaneId) {
177
+ pane = document.getElementById(targetPaneId);
178
+ if (!pane) return;
179
+
180
+ const unreadItems = Array.from(pane.querySelectorAll('.notif-item')).filter(item =>
181
+ item.style.background.includes('rgba') || item.style.background.includes('var')
182
+ );
183
+ idsToMark = unreadItems.map(item => item.dataset.id).filter(id => id);
184
+ }
185
+ }
186
+
187
+ if (idsToMark.length === 0) return;
188
+
189
+ try {
190
+ const formData = new FormData();
191
+ if (document.querySelector('meta[name="csrf-token"]')) formData.append('csrf_token', document.querySelector('meta[name="csrf-token"]').content);
192
+ formData.append('action', 'markAsRead');
193
+ idsToMark.forEach(id => formData.append('ids[]', id));
194
+
195
+ await fetch('/api/switch-case/notification', {
196
+ method: 'POST',
197
+ body: formData
198
+ });
199
+
200
+ unreadIds = unreadIds.filter(id => !idsToMark.includes(id));
201
+
202
+ if (unreadIds.length === 0) {
203
+ notifBadge.style.display = 'none';
204
+ if (markReadBtn) markReadBtn.style.display = 'none';
205
+ }
206
+
207
+ // Sadece hedeflenen bildirimlerin DOM stilini temizle
208
+ idsToMark.forEach(id => {
209
+ const item = document.querySelector(`.notif-item[data-id="${id}"]`);
210
+ if (item) {
211
+ item.style.background = 'transparent';
212
+ item.style.borderLeft = '3px solid transparent';
213
+ item.style.opacity = '0.7';
214
+
215
+ const titleEl = item.querySelector('strong');
216
+ if (titleEl && titleEl.innerHTML.includes('•')) {
217
+ titleEl.innerHTML = titleEl.innerHTML.replace(/<span.*?•<\/span>\s*/, '');
218
+ }
219
+ }
220
+ });
221
+
222
+ // İlgili sekmelerin kırmızı noktasını temizle
223
+ if (targetPaneId && targetPaneId !== 'all') {
224
+ const tabBtn = document.querySelector(`.notif-tab[data-target="${targetPaneId}"]`);
225
+ if (tabBtn) {
226
+ const dot = tabBtn.querySelector('.notif-dot');
227
+ if (dot) dot.remove();
228
+ }
229
+ } else if (targetPaneId === 'all') {
230
+ document.querySelectorAll('.notif-tab .notif-dot').forEach(dot => dot.remove());
231
+ }
232
+
233
+ } catch (e) {
234
+ console.error('Bildirimler işaretlenemedi', e);
235
+ }
236
+ };
237
+
238
+ if (notifBtn && notifMenu) {
239
+ fetchNotifications(); // Sayfa yüklendiğinde çek
240
+
241
+ // Tab geçiş mantığı
242
+ notifTabs.forEach(tab => {
243
+ tab.addEventListener('click', (e) => {
244
+ e.preventDefault();
245
+ e.stopPropagation();
246
+
247
+ notifTabs.forEach(t => {
248
+ t.classList.remove('active');
249
+ t.style.color = 'var(--text-muted)';
250
+ t.style.borderBottom = '2px solid transparent';
251
+ });
252
+
253
+ tab.classList.add('active');
254
+ tab.style.color = 'var(--brand-primary)';
255
+ tab.style.borderBottom = '2px solid var(--brand-primary)';
256
+
257
+ document.querySelectorAll('.notif-content-pane').forEach(pane => {
258
+ pane.style.display = 'none';
259
+ });
260
+ document.getElementById(tab.dataset.target).style.display = 'flex';
261
+
262
+ // Sekme değiştiğinde sadece o sekmeyi okundu yap
263
+ if (unreadIds.length > 0) {
264
+ markAsRead(tab.dataset.target);
265
+ }
266
+ });
267
+ });
268
+
269
+ notifBtn.addEventListener('click', (e) => {
270
+ e.stopPropagation();
271
+ const isShowing = notifMenu.classList.contains('show');
272
+
273
+ // Diğer menüyü kapat
274
+ if (profileMenu && profileMenu.classList.contains('show')) {
275
+ profileMenu.classList.remove('show');
276
+ }
277
+
278
+ notifMenu.classList.toggle('show');
279
+
280
+ // Menü açıldığında okundu işaretle ve lazy-load resimleri yükle
281
+ if (!isShowing) {
282
+ document.querySelectorAll('.lazy-notif-avatar[data-src]').forEach(img => {
283
+ img.src = img.getAttribute('data-src');
284
+ img.removeAttribute('data-src');
285
+ });
286
+
287
+ if (unreadIds.length > 0) {
288
+ markAsRead();
289
+ }
290
+ }
291
+ });
292
+
293
+ document.addEventListener('click', (e) => {
294
+ if (!notifBtn.contains(e.target) && !notifMenu.contains(e.target)) {
295
+ notifMenu.classList.remove('show');
296
+ }
297
+ });
298
+
299
+ if (markReadBtn) {
300
+ markReadBtn.addEventListener('click', (e) => {
301
+ e.stopPropagation();
302
+ markAsRead('all');
303
+ });
304
+ }
305
+ }
306
+
307
+ // WebSocket Real-Time Notifications
308
+ if (window.WS_USER_ID && window.WS_TOKEN) {
309
+ let wsProto = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
310
+ let wsUrl = wsProto + window.location.hostname + '/ws/';
311
+
312
+ let ws = new WebSocket(wsUrl);
313
+
314
+ // Bildirim sesini sayfaya ilk girişte R2'den çekip önbelleğe alıyoruz.
315
+ let notifSound = new Audio('https://sinavortagim.artilingo.com/system/notification.mp3');
316
+ notifSound.preload = 'auto';
317
+ notifSound.load();
318
+
319
+ // Tarayıcı otomatik çalma engelini aşmak (Audio Unlock) için:
320
+ // Kullanıcının sayfadaki ilk etkileşiminde (tıklama vb.) sesi sessizce çalıp yetki alırız.
321
+ const unlockAudio = () => {
322
+ if (notifSound) {
323
+ notifSound.muted = true;
324
+ notifSound.play().then(() => {
325
+ notifSound.pause();
326
+ notifSound.currentTime = 0;
327
+ notifSound.muted = false;
328
+ }).catch(e => {});
329
+ }
330
+ document.removeEventListener('click', unlockAudio);
331
+ document.removeEventListener('keydown', unlockAudio);
332
+ };
333
+ document.addEventListener('click', unlockAudio);
334
+ document.addEventListener('keydown', unlockAudio);
335
+
336
+ ws.onopen = () => {
337
+ ws.send(JSON.stringify({
338
+ type: 'auth',
339
+ userIdHex: window.WS_USER_ID,
340
+ hash: window.WS_TOKEN
341
+ }));
342
+ };
343
+
344
+ ws.onmessage = (event) => {
345
+ try {
346
+ let res = JSON.parse(event.data);
347
+ if (res.type === 'new_notification') {
348
+ // 1. Sesi çal (Tarayıcı engelini aşmak için try-catch ile)
349
+ if (notifSound) {
350
+ notifSound.currentTime = 0;
351
+ notifSound.play().catch(e => {
352
+ console.warn("Bildirim sesi otomatik oynatılamadı. Tarayıcı etkileşim istiyor olabilir:", e.message);
353
+ });
354
+ }
355
+
356
+ // 2. Zile Parlama/Sallanma animasyonu ver
357
+ const bellIcon = document.querySelector('#notificationBtn i');
358
+ if (bellIcon) {
359
+ bellIcon.classList.remove('fa-regular');
360
+ bellIcon.classList.add('fa-solid', 'fa-shake');
361
+ bellIcon.style.color = 'var(--brand-primary)';
362
+
363
+ setTimeout(() => {
364
+ bellIcon.classList.remove('fa-solid', 'fa-shake');
365
+ bellIcon.classList.add('fa-regular');
366
+ bellIcon.style.color = '';
367
+ }, 2000);
368
+ }
369
+
370
+ // Sunucudan (PHP API) profil fotoğrafları ve tam bildirim ID'leri ile en güncel listeyi sessizce çek
371
+ if (typeof fetchNotifications === 'function') {
372
+ fetchNotifications();
373
+ }
374
+ }
375
+ } catch (e) {
376
+ console.error('WS Error:', e);
377
+ }
378
+ };
379
+ }
380
+ });
@@ -0,0 +1,97 @@
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const mobileMenuBtn = document.getElementById('mobileMenuBtn');
3
+ const mobileBottomSheet = document.getElementById('mobileBottomSheet');
4
+ const sheetOverlay = document.getElementById('sheetOverlay');
5
+
6
+ if (mobileMenuBtn && mobileBottomSheet) {
7
+ const sheetContent = mobileBottomSheet.querySelector('.sheet-content');
8
+
9
+ let scrollPosition = 0;
10
+
11
+ const closeMenu = () => {
12
+ mobileBottomSheet.classList.remove('is-open');
13
+ document.documentElement.style.overflow = '';
14
+ document.body.style.overflow = '';
15
+ document.body.style.overscrollBehaviorY = '';
16
+ document.body.style.position = '';
17
+ document.body.style.top = '';
18
+ document.body.style.width = '';
19
+ window.scrollTo(0, scrollPosition);
20
+ };
21
+
22
+ const openMenu = () => {
23
+ scrollPosition = window.scrollY;
24
+ mobileBottomSheet.classList.add('is-open');
25
+ document.documentElement.style.overflow = 'hidden';
26
+ document.body.style.overflow = 'hidden';
27
+ document.body.style.overscrollBehaviorY = 'none';
28
+ document.body.style.position = 'fixed';
29
+ document.body.style.top = `-${scrollPosition}px`;
30
+ document.body.style.width = '100%';
31
+ };
32
+
33
+ // Menüyü Aç / Kapa (Toggle)
34
+ mobileMenuBtn.addEventListener('click', (e) => {
35
+ e.preventDefault();
36
+ if (mobileBottomSheet.classList.contains('is-open')) {
37
+ closeMenu();
38
+ } else {
39
+ openMenu();
40
+ }
41
+ });
42
+
43
+ // Overlay'e tıklanınca Menüyü Kapat
44
+ if (sheetOverlay) {
45
+ sheetOverlay.addEventListener('click', closeMenu);
46
+ sheetOverlay.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false });
47
+ }
48
+
49
+ // Aşağı Kaydırarak (Swipe) Kapatma
50
+ if (sheetContent) {
51
+ let startY = 0;
52
+ let currentY = 0;
53
+ let isDragging = false;
54
+ let canCloseOnSwipe = false;
55
+
56
+ sheetContent.addEventListener('touchstart', (e) => {
57
+ startY = e.touches[0].clientY;
58
+ isDragging = true;
59
+ sheetContent.style.transition = 'none';
60
+
61
+ // Kaydırma hareketinin BAŞLANGICINDA en üstte miyiz kontrol et
62
+ const sheetBody = sheetContent.querySelector('.sheet-body');
63
+ if (sheetBody && sheetBody.contains(e.target)) {
64
+ canCloseOnSwipe = sheetBody.scrollTop <= 0;
65
+ } else {
66
+ canCloseOnSwipe = true;
67
+ }
68
+ }, { passive: true });
69
+
70
+ sheetContent.addEventListener('touchmove', (e) => {
71
+ if (!isDragging) return;
72
+ currentY = e.touches[0].clientY;
73
+ const diff = currentY - startY;
74
+
75
+ // Sadece hareket ilk başladığında en üstteysek menüyü kapatmaya izin ver
76
+ if (diff > 0 && canCloseOnSwipe) {
77
+ if (e.cancelable) e.preventDefault();
78
+ sheetContent.style.transform = `translateY(${diff}px)`;
79
+ }
80
+ }, { passive: false });
81
+
82
+ sheetContent.addEventListener('touchend', (e) => {
83
+ if (!isDragging) return;
84
+ isDragging = false;
85
+ sheetContent.style.transition = '';
86
+
87
+ const diff = currentY - startY;
88
+ if (diff > 100 && canCloseOnSwipe) {
89
+ sheetContent.style.transform = '';
90
+ closeMenu();
91
+ } else {
92
+ sheetContent.style.transform = '';
93
+ }
94
+ });
95
+ }
96
+ }
97
+ });
@@ -0,0 +1,74 @@
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const htmlElement = document.documentElement;
3
+
4
+ // Tüm tema destekli resimleri güncelleyen fonksiyon
5
+ const updateThemeImages = () => {
6
+ const currentMode = htmlElement.getAttribute('data-mode');
7
+ document.querySelectorAll('img.theme-image').forEach(img => {
8
+ const newSrc = currentMode === 'dark' ? img.getAttribute('data-dark-src') : img.getAttribute('data-light-src');
9
+ if (newSrc) {
10
+ // Sadece son dosya ismini (veya tam yolu) kontrol ederek gereksiz src değişimini önle
11
+ if (!img.src.endsWith(newSrc) && img.getAttribute('src') !== newSrc) {
12
+ img.src = newSrc;
13
+ }
14
+ }
15
+ });
16
+ };
17
+
18
+ // Sayfa yüklendiğinde resimleri ayarla
19
+ updateThemeImages();
20
+
21
+ // Tema değişikliğini dinle (HTML tag'indeki data-mode attribute'u değiştiğinde)
22
+ const observer = new MutationObserver((mutations) => {
23
+ mutations.forEach((mutation) => {
24
+ if (mutation.type === 'attributes' && mutation.attributeName === 'data-mode') {
25
+ updateThemeImages();
26
+ }
27
+ });
28
+ });
29
+ observer.observe(htmlElement, { attributes: true });
30
+
31
+ const themeToggleBtn = document.getElementById('themeToggleBtn');
32
+
33
+ if (themeToggleBtn) {
34
+ // İkonu güncel moda göre ayarla
35
+ const updateIcon = () => {
36
+ const currentMode = htmlElement.getAttribute('data-mode');
37
+ const icon = themeToggleBtn.querySelector('i');
38
+
39
+ if (icon) {
40
+ if (currentMode === 'dark') {
41
+ icon.className = 'fa-solid fa-sun';
42
+ } else {
43
+ icon.className = 'fa-solid fa-moon';
44
+ }
45
+ }
46
+ };
47
+
48
+ // Sayfa yüklendiğinde doğru ikonu göster
49
+ updateIcon();
50
+
51
+ // observer ile butonu senkronize tut
52
+ const btnObserver = new MutationObserver((mutations) => {
53
+ mutations.forEach((mutation) => {
54
+ if (mutation.type === 'attributes' && mutation.attributeName === 'data-mode') {
55
+ updateIcon();
56
+ }
57
+ });
58
+ });
59
+ btnObserver.observe(htmlElement, { attributes: true });
60
+
61
+ // Butona tıklandığında temayı değiştir
62
+ themeToggleBtn.addEventListener('click', () => {
63
+ const currentMode = htmlElement.getAttribute('data-mode');
64
+ const newMode = currentMode === 'dark' ? 'light' : 'dark';
65
+
66
+ // HTML etiketine data-mode olarak yaz
67
+ htmlElement.setAttribute('data-mode', newMode);
68
+
69
+ // Kullanıcının tercihini localStorage'a kaydet (Bir sonraki girişte hatırlanır)
70
+ localStorage.setItem('app-mode', newMode);
71
+ localStorage.setItem('artiframe_theme_mode', newMode); // root app.js ile senkronize
72
+ });
73
+ }
74
+ });