@artilingo/artiframe-cli 2.0.4 → 3.0.0
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/LICENSE +660 -660
- package/README.md +64 -118
- package/bin/artiframe.js +0 -0
- package/bin/artiframe.php +0 -0
- package/core-stubs/app/ApiAuthControl.php +76 -0
- package/core-stubs/app/ApiControl.php +81 -35
- package/core-stubs/app/AuthControl.php +39 -0
- package/core-stubs/app/Database.php +36 -35
- package/core-stubs/app/DotEnv.php +19 -1
- package/core-stubs/app/ViewControl.php +47 -1
- package/core-stubs/bin/SQLMethod.php +261 -0
- package/core-stubs/bin/SystemMethod.php +115 -74
- package/core-stubs/bin/ViewMethod.php +19 -5
- package/core-stubs/docs/de.html +4325 -4347
- package/core-stubs/docs/en.html +4325 -4347
- package/core-stubs/docs/es.html +4325 -4347
- package/core-stubs/docs/fr.html +4325 -4347
- package/core-stubs/docs/tr.html +117 -46
- package/core-stubs/public/403.php +52 -0
- package/core-stubs/public/assets/css/components/header.css +174 -0
- package/core-stubs/public/assets/css/components/mobilenav.css +198 -0
- package/core-stubs/public/assets/css/components/sidebar.css +184 -0
- package/core-stubs/public/assets/css/root/app.css +975 -38
- package/core-stubs/public/assets/css/root/root.css +64 -0
- package/core-stubs/public/assets/js/components/header.js +380 -0
- package/core-stubs/public/assets/js/components/mobilenav.js +97 -0
- package/core-stubs/public/assets/js/components/theme-modal.js +74 -0
- package/core-stubs/public/assets/js/root/app.js +1870 -0
- package/core-stubs/public/includes/footer.php +4 -5
- package/core-stubs/public/includes/head-auth.php +65 -0
- package/core-stubs/public/includes/head.php +56 -17
- package/core-stubs/public/includes/header.php +135 -1
- package/core-stubs/public/includes/mobilenav.php +75 -3
- package/core-stubs/public/includes/sidebar.php +186 -1
- package/core-stubs/public/includes/theme-modal.php +1 -0
- package/db-studio/debug.log +3 -0
- package/db-studio/index.html +1755 -0
- package/db-studio/launcher.html +102 -0
- package/db-studio/launcher.js +180 -0
- package/db-studio/logo.svg +22 -0
- package/db-studio/main.js +568 -0
- package/db-studio/package-lock.json +1034 -0
- package/db-studio/package.json +16 -0
- package/db-studio/preload.js +29 -0
- package/devops-studio/api-inspector.js +222 -0
- package/devops-studio/index.html +1892 -0
- package/devops-studio/logo.svg +22 -0
- package/devops-studio/main.js +67 -0
- package/devops-studio/make-wizard.js +635 -0
- package/devops-studio/package-lock.json +871 -0
- package/devops-studio/package.json +12 -0
- package/package.json +5 -2
- package/scripts/postinstall.js +44 -8
- package/server-studio/.json-404-guard +1 -0
- package/server-studio/index.html +283 -0
- package/server-studio/logo.svg +22 -0
- package/server-studio/main.js +133 -0
- package/server-studio/package-lock.json +1179 -0
- package/server-studio/package.json +15 -0
- package/server-studio/router.php +99 -0
- package/src/App.php +29 -0
- package/src/Commands/AddCommand.php +26 -9
- package/src/Commands/DbExportCommand.php +131 -0
- package/src/Commands/DbStartCommand.php +46 -0
- package/src/Commands/DevopsCommand.php +35 -0
- package/src/Commands/GoCommand.php +48 -48
- package/src/Commands/ListCommand.php +68 -68
- package/src/Commands/MakeApiCommand.php +6 -1
- package/src/Commands/MakeViewCommand.php +25 -2
- package/src/Commands/NewProjectCommand.php +16 -11
- package/src/Commands/OptimizeCommand.php +61 -0
- package/src/Commands/RemoveCommand.php +35 -7
- package/src/Commands/ServeCommand.php +46 -91
- package/src/Commands/ShowCommand.php +24 -24
- package/src/Commands/UpgradeCommand.php +39 -10
- package/stubs/make/api-switch-case.stub +1 -1
- package/stubs/make/auth-api-standart.stub +20 -0
- package/stubs/make/auth-api-switch-case.stub +38 -0
- package/stubs/make/auth-view.stub +26 -0
- package/stubs/make/class.stub +10 -2
- package/stubs/make/view.stub +3 -0
- package/stubs/service/image.stub +334 -334
- package/stubs/service/iyzico.stub +637 -637
- package/stubs/service/jwt.stub +312 -312
- package/stubs/service/phpmailer.stub +143 -143
- package/stubs/service/pusher.stub +232 -232
- package/stubs/service/qrcode.stub +169 -169
- package/stubs/service/redis.stub +361 -361
- package/stubs/service/s3.stub +377 -377
- package/stubs/service/stripe.stub +526 -526
- 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
|
+
});
|