// ============================================================================ // ⚙️ التكوين العام - AppConfig + Firebase + AppState (v9.7 المُحسَّن) // ✅ تحديثات v9.6: تهيئة مؤجلة ومنع التحميل المزدوج والتوافق مع ConnectionManager // ✅ تحديثات v9.7 المضافة: // - إصلاح الفرز المقاوم للنصوص والتواريخ الهجينة داخل دالة applyFullOrdering // - حماية التراجع الذكي (Fallback) لمنع تصفير مصفوفات التثبيت والترتيب عند بطء IndexedDB // - إلغاء قنوات الجلب المكررة للسيرفر عند استعادة الشبكة (ترك المهام لـ Loader) // - تأمين تصدير كامل البيانات (allUsers و allActivityLogs) في النسخة الاحتياطية المحلية // إعداد أبو علي © 2026 - شعارنا: "المعرفة بين يديك" // ============================================================================ window.AppConfigContainer = { firebaseConfig: { apiKey: "AIzaSyCfELZeuNe_hsqSrqgM99OydwPDz7JXWRs", authDomain: "student-library-5d69e.firebaseapp.com", databaseURL: "https://student-library-5d69e-default-rtdb.firebaseio.com", projectId: "student-library-5d69e", storageBucket: "student-library-5d69e.firebasestorage.app", messagingSenderId: "720859041094", appId: "1:720859041094:web:cb1293d08777c2b9b96947", measurementId: "G-X47W9T8E6F" }, _dbInstance: null, _firebaseInitialized: false, _networkListenersSetup: false, _initAttempted: false, _pinnedLoaded: false, _orderingLoaded: false, get isProduction() { return window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1'; }, ROLES: { 'super_admin': { label: 'مدير رئيسي', icon: 'fa-crown', level: 4, canEdit: true, canDelete: true, canCreate: true, canUsers: true, canSettings: true, canBackup: true, canNavigation: true, canAccessAdmin: true, canSeePassword: true, canViewActivityLog: true, canDeleteActivityLog: true, canManageCalendar: true, canDeleteComments: true }, 'admin': { label: 'مدير نظام', icon: 'fa-user-shield', level: 3, canEdit: true, canDelete: true, canCreate: true, canUsers: false, canSettings: false, canBackup: false, canNavigation: false, canAccessAdmin: true, canSeePassword: false, canViewActivityLog: false, canDeleteActivityLog: false, canManageCalendar: true, canDeleteComments: true }, 'editor': { label: 'محرر محتوى', icon: 'fa-pen-nib', level: 2, canEdit: true, canDelete: false, canCreate: true, canUsers: false, canSettings: false, canBackup: false, canNavigation: false, canAccessAdmin: true, canSeePassword: false, canViewActivityLog: false, canDeleteActivityLog: false, canManageCalendar: true, canDeleteComments: false }, 'viewer': { label: 'مشاهد', icon: 'fa-eye', level: 1, canEdit: false, canDelete: false, canCreate: false, canUsers: false, canSettings: false, canBackup: false, canNavigation: false, canAccessAdmin: false, canSeePassword: false, canViewActivityLog: false, canDeleteActivityLog: false, canManageCalendar: false, canDeleteComments: false } }, _log(...args) { if (!this.isProduction) console.log('[AppConfig]', ...args); }, isOnline() { if (window.ConnectionManager && typeof window.ConnectionManager.isOnline === 'function') { return window.ConnectionManager.isOnline(); } return navigator.onLine; }, getDB() { if (this._dbInstance) return this._dbInstance; if (window.database) { this._dbInstance = window.database; return this._dbInstance; } if (typeof firebase !== 'undefined' && firebase.apps && firebase.apps.length > 0) { try { this._dbInstance = firebase.database(); return this._dbInstance; } catch(e) { console.error('❌ فشل في الحصول على قاعدة البيانات:', e); } } return null; }, async _fetchWithTimeout(ref, timeoutMs = 4000) { try { return await Promise.race([ ref.once('value').then(s => s.val()), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeoutMs) ) ]); } catch (error) { this._log('⏱️ مهلة الاتصال:', error.message); return null; } }, async initFirebase() { if (this._firebaseInitialized && window.database) { this._dbInstance = window.database; return true; } if (this._initAttempted && !this._firebaseInitialized) { if (typeof firebase === 'undefined') return false; } this._initAttempted = true; if (!window.AppConfig) { window.AppConfig = { firebaseConfig: this.firebaseConfig, ROLES: this.ROLES }; } if (!window.PinnedSettings) { window.PinnedSettings = { books: [], quizzes: [], articles: [] }; } if (!window.OrderingSettings) { window.OrderingSettings = { books: [], quizzes: [], articles: [] }; } try { if (typeof firebase !== 'undefined') { if (!firebase.apps || firebase.apps.length === 0) { firebase.initializeApp(this.firebaseConfig); this._log('✅ Firebase initialized successfully'); } this._firebaseInitialized = true; window.firebaseInitialized = true; window.auth = firebase.auth(); window.database = firebase.database(); this._dbInstance = window.database; return true; } else { this._log('⚠️ Firebase SDK غير محمل بعد - سيتم إعادة المحاولة عند توفره'); return false; } } catch (error) { if (error.code === 'app/duplicate-app') { this._firebaseInitialized = true; window.firebaseInitialized = true; if (!window.database && firebase.apps.length > 0) { window.database = firebase.database(); this._dbInstance = window.database; } return true; } this._log('❌ فشل تهيئة Firebase:', error); return false; } }, async retryInitFirebase() { if (this._firebaseInitialized) return true; return await this.initFirebase(); }, async savePinnedItems(type, items) { try { if (type === 'books') window.PinnedSettings.books = [...items]; else if (type === 'quizzes') window.PinnedSettings.quizzes = [...items]; else if (type === 'articles') window.PinnedSettings.articles = [...items]; if (window.OfflineStorage) { await window.OfflineStorage.saveSetting(`pinned_${type}`, items); } localStorage.setItem(`pinned_${type}`, JSON.stringify(items)); const db = this.getDB(); if (this.isOnline() && db) { await db.ref(`pinned/${type}`).set(items).catch(e => this._log('⚠️ فشل حفظ التثبيت سحابياً:', e)); } return true; } catch (error) { console.error('خطأ في حفظ التثبيت:', error); return false; } }, async loadPinnedItems() { if (this._pinnedLoaded && window.PinnedSettings && (window.PinnedSettings.books?.length || window.PinnedSettings.quizzes?.length || window.PinnedSettings.articles?.length)) { return window.PinnedSettings; } try { if (this.isOnline()) { const db = this.getDB(); if (db) { const serverData = await this._fetchWithTimeout(db.ref('pinned')); if (serverData) { window.PinnedSettings.books = serverData.books || []; window.PinnedSettings.quizzes = serverData.quizzes || []; window.PinnedSettings.articles = serverData.articles || []; this._pinnedLoaded = true; await this._savePinnedToLocal(); return window.PinnedSettings; } } } return await this._loadPinnedFromLocal(); } catch (error) { return await this._loadPinnedFromLocal(); } }, async _savePinnedToLocal() { if (window.OfflineStorage) { await Promise.allSettled([ window.OfflineStorage.saveSetting('pinned_books', window.PinnedSettings.books), window.OfflineStorage.saveSetting('pinned_quizzes', window.PinnedSettings.quizzes), window.OfflineStorage.saveSetting('pinned_articles', window.PinnedSettings.articles) ]); } localStorage.setItem('pinned_books', JSON.stringify(window.PinnedSettings.books)); localStorage.setItem('pinned_quizzes', JSON.stringify(window.PinnedSettings.quizzes)); localStorage.setItem('pinned_articles', JSON.stringify(window.PinnedSettings.articles)); }, // 🛠️ [v9.7 تحسين]: دمج ذكي للكاش المحلي لمنع تصفير مصفوفات التثبيت في حال تعليق أو بطء IndexedDB async _loadPinnedFromLocal() { let localLoaded = false; if (window.OfflineStorage) { try { const [books, quizzes, articles] = await Promise.allSettled([ window.OfflineStorage.getSetting('pinned_books'), window.OfflineStorage.getSetting('pinned_quizzes'), window.OfflineStorage.getSetting('pinned_articles') ]); if (books.status === 'fulfilled' && books.value) { window.PinnedSettings.books = books.value; localLoaded = true; } if (quizzes.status === 'fulfilled' && quizzes.value) { window.PinnedSettings.quizzes = quizzes.value; localLoaded = true; } if (articles.status === 'fulfilled' && articles.value) { window.PinnedSettings.articles = articles.value; localLoaded = true; } } catch (e) {} } if (!localLoaded || (!window.PinnedSettings.books?.length && !window.PinnedSettings.quizzes?.length && !window.PinnedSettings.articles?.length)) { this._loadPinnedFromLocalStorage(); } this._pinnedLoaded = true; return window.PinnedSettings; }, _loadPinnedFromLocalStorage() { try { window.PinnedSettings.books = JSON.parse(localStorage.getItem('pinned_books') || '[]'); window.PinnedSettings.quizzes = JSON.parse(localStorage.getItem('pinned_quizzes') || '[]'); window.PinnedSettings.articles = JSON.parse(localStorage.getItem('pinned_articles') || '[]'); } catch (e) { window.PinnedSettings.books = []; window.PinnedSettings.quizzes = []; window.PinnedSettings.articles = []; } }, async togglePinItem(type, itemId) { let currentPinned = []; let normType = type; if (type === 'books' || type === 'book') { normType = 'books'; currentPinned = [...(window.PinnedSettings?.books || [])]; } else if (type === 'quizzes' || type === 'quiz') { normType = 'quizzes'; currentPinned = [...(window.PinnedSettings?.quizzes || [])]; } else if (type === 'articles' || type === 'article') { normType = 'articles'; currentPinned = [...(window.PinnedSettings?.articles || [])]; } else { return currentPinned; } const index = currentPinned.indexOf(itemId); if (index === -1) { currentPinned.push(itemId); } else { currentPinned.splice(index, 1); } await this.savePinnedItems(normType, currentPinned); return currentPinned; }, isItemPinned(type, itemId) { if (!window.PinnedSettings) return false; if (type === 'book' || type === 'books') return window.PinnedSettings.books?.includes(itemId) || false; if (type === 'quiz' || type === 'quizzes') return window.PinnedSettings.quizzes?.includes(itemId) || false; if (type === 'article' || type === 'articles') return window.PinnedSettings.articles?.includes(itemId) || false; return false; }, getPinnedItems(type, allItems) { if (!allItems || !Array.isArray(allItems)) return []; let pinnedIds = []; if (type === 'books' || type === 'book') { pinnedIds = window.PinnedSettings?.books || []; } else if (type === 'quizzes' || type === 'quiz') { pinnedIds = window.PinnedSettings?.quizzes || []; } else if (type === 'articles' || type === 'article') { pinnedIds = window.PinnedSettings?.articles || []; } else { return []; } return pinnedIds .map(id => allItems.find(i => i && i.id === id)) .filter(i => i !== undefined); }, async saveOrdering(type, orderedIds) { try { if (type === 'books') window.OrderingSettings.books = [...orderedIds]; else if (type === 'quizzes') window.OrderingSettings.quizzes = [...orderedIds]; else if (type === 'articles') window.OrderingSettings.articles = [...orderedIds]; if (window.OfflineStorage) { await window.OfflineStorage.saveSetting(`ordering_${type}`, orderedIds); } localStorage.setItem(`ordering_${type}`, JSON.stringify(orderedIds)); const db = this.getDB(); if (this.isOnline() && db) { await db.ref(`ordering/${type}`).set(orderedIds).catch(e => this._log('⚠️ فشل حفظ الترتيب سحابياً:', e)); } return true; } catch (error) { console.error('خطأ في حفظ الترتيب:', error); return false; } }, async loadOrdering() { if (this._orderingLoaded && window.OrderingSettings && (window.OrderingSettings.books?.length || window.OrderingSettings.quizzes?.length || window.OrderingSettings.articles?.length)) { return window.OrderingSettings; } try { if (this.isOnline()) { const db = this.getDB(); if (db) { const serverData = await this._fetchWithTimeout(db.ref('ordering')); if (serverData) { window.OrderingSettings.books = serverData.books || []; window.OrderingSettings.quizzes = serverData.quizzes || []; window.OrderingSettings.articles = serverData.articles || []; this._orderingLoaded = true; await this._saveOrderingToLocal(); return window.OrderingSettings; } } } return await this._loadOrderingFromLocal(); } catch (error) { return await this._loadOrderingFromLocal(); } }, async _saveOrderingToLocal() { if (window.OfflineStorage) { await Promise.allSettled([ window.OfflineStorage.saveSetting('ordering_books', window.OrderingSettings.books), window.OfflineStorage.saveSetting('ordering_quizzes', window.OrderingSettings.quizzes), window.OfflineStorage.saveSetting('ordering_articles', window.OrderingSettings.articles) ]); } localStorage.setItem('ordering_books', JSON.stringify(window.OrderingSettings.books)); localStorage.setItem('ordering_quizzes', JSON.stringify(window.OrderingSettings.quizzes)); localStorage.setItem('ordering_articles', JSON.stringify(window.OrderingSettings.articles)); }, // 🛠️ [v9.7 تحسين]: حماية التراجع الذكي لمنع تصفير مصفوفات الترتيب المخصص عند بطء التجاوب لـ IndexedDB async _loadOrderingFromLocal() { let localLoaded = false; if (window.OfflineStorage) { try { const [books, quizzes, articles] = await Promise.allSettled([ window.OfflineStorage.getSetting('ordering_books'), window.OfflineStorage.getSetting('ordering_quizzes'), window.OfflineStorage.getSetting('ordering_articles') ]); if (books.status === 'fulfilled' && books.value) { window.OrderingSettings.books = books.value; localLoaded = true; } if (quizzes.status === 'fulfilled' && quizzes.value) { window.OrderingSettings.quizzes = books.value; localLoaded = true; } if (articles.status === 'fulfilled' && articles.value) { window.OrderingSettings.articles = books.value; localLoaded = true; } } catch (e) {} } if (!localLoaded || (!window.OrderingSettings.books?.length && !window.OrderingSettings.quizzes?.length && !window.OrderingSettings.articles?.length)) { this._loadOrderingFromLocalStorage(); } this._orderingLoaded = true; return window.OrderingSettings; }, _loadOrderingFromLocalStorage() { try { window.OrderingSettings.books = JSON.parse(localStorage.getItem('ordering_books') || '[]'); window.OrderingSettings.quizzes = JSON.parse(localStorage.getItem('ordering_quizzes') || '[]'); window.OrderingSettings.articles = JSON.parse(localStorage.getItem('ordering_articles') || '[]'); } catch (e) { window.OrderingSettings.books = []; window.OrderingSettings.quizzes = []; window.OrderingSettings.articles = []; } }, // 🛠️ [v9.7 تصحيح]: دعم كامل وتحليل آمن للتواريخ الهجينة (ISO Strings و Timestamps) منعاً لقيم NaN الفاسدة للترتيب applyFullOrdering(items, type, sortField = 'createdAt') { if (!items || !Array.isArray(items) || !items.length) return []; window.PinnedSettings = window.PinnedSettings || { books: [], quizzes: [], articles: [] }; window.OrderingSettings = window.OrderingSettings || { books: [], quizzes: [], articles: [] }; let pinnedIds = []; let customOrderIds = []; if (type === 'books' || type === 'book') { pinnedIds = window.PinnedSettings.books || []; customOrderIds = window.OrderingSettings.books || []; } else if (type === 'quizzes' || type === 'quiz') { pinnedIds = window.PinnedSettings.quizzes || []; customOrderIds = window.OrderingSettings.quizzes || []; } else if (type === 'articles' || type === 'article') { pinnedIds = window.PinnedSettings.articles || []; customOrderIds = window.OrderingSettings.articles || []; } const pinnedItems = []; const normalItems = []; items.forEach(item => { if (item && item.id && pinnedIds.includes(item.id)) { pinnedItems.push(item); } else if (item) { normalItems.push(item); } }); pinnedItems.sort((a, b) => pinnedIds.indexOf(a.id) - pinnedIds.indexOf(b.id)); normalItems.sort((a, b) => { const indexA = customOrderIds.indexOf(a.id); const indexB = customOrderIds.indexOf(b.id); if (indexA !== -1 && indexB !== -1) return indexA - indexB; if (indexA !== -1) return -1; if (indexB !== -1) return 1; const dateA = typeof a[sortField] === 'string' ? Date.parse(a[sortField]) : (a[sortField] || 0); const dateB = typeof b[sortField] === 'string' ? Date.parse(b[sortField]) : (b[sortField] || 0); return dateB - dateA; }); return [...pinnedItems, ...normalItems]; }, hasPermissionByLevel(requiredLevel) { if (!window.AppState?.currentUser) return false; const userRole = window.AppState.currentUser.role || 'viewer'; const roleLevel = { 'super_admin': 4, 'admin': 3, 'editor': 2, 'viewer': 1 }; return (roleLevel[userRole] || 0) >= requiredLevel; }, canDeleteComments() { if (!window.AppState?.currentUser) return false; const userRole = window.AppState.currentUser.role || 'viewer'; return this.ROLES[userRole]?.canDeleteComments === true; }, canDeleteSpecificComment(comment) { if (!comment || !window.AppState?.currentUser) return false; const userRole = window.AppState.currentUser.role || 'viewer'; if (this.ROLES[userRole]?.canDeleteComments === true) return true; const currentUserId = window.AppState.currentUser.id || window.AppState.currentUser.uid || window.AppState.currentUser.email; const commentUserId = comment.userId || comment.uid || comment.email; return commentUserId === currentUserId; }, async exportSystemBackup() { if (!this.hasPermissionByLevel(4)) { console.warn('⚠️ لا تملك صلاحية تصدير النسخة الاحتياطية'); return false; } try { let backupPayload = null; const db = this.getDB(); if (this.isOnline() && db) { try { const fullData = await this._fetchWithTimeout(db.ref(), 10000); if (fullData) { backupPayload = { metadata: { version: "3.0", exportedAt: Date.now(), system: "Madwanaty Uni", source: "Cloud" }, books: fullData.books || {}, quizzes: fullData.quizzes || {}, articles: fullData.articles || {}, comments: fullData.comments || {}, settings: fullData.settings || {}, navigation: fullData.navigation || {}, pinned: fullData.pinned || {}, ordering: fullData.ordering || {}, calendarEvents: fullData.calendarEvents || [], users: fullData.users || {} }; } } catch (e) { this._log('⏱️ مهلة السيرفر للتصدير السحابي'); } } // 🛠️ [v9.7 تحسين]: إدراج مصفوفة الـ users والـ activityLog كاملة أوفلاين لحفظ التراخيص والإحصائيات if (!backupPayload) { const state = window.AppState || {}; const arrayToObj = (arr) => { const obj = {}; if (Array.isArray(arr)) { arr.forEach(i => { if (i && i.id) obj[i.id] = { ...i }; }); } return obj; }; backupPayload = { metadata: { version: "3.0", exportedAt: Date.now(), system: "Madwanaty Uni", source: "Offline-Local" }, books: arrayToObj(state.allBooks), quizzes: arrayToObj(state.allQuizzes), articles: arrayToObj(state.allArticles), comments: arrayToObj(state.allComments), users: arrayToObj(state.allUsers), activityLogs: arrayToObj(state.allActivityLogs), settings: state.siteSettings || {}, navigation: state.navigationSettings || {}, pinned: { books: window.PinnedSettings?.books || [], quizzes: window.PinnedSettings?.quizzes || [], articles: window.PinnedSettings?.articles || [] }, ordering: { books: window.OrderingSettings?.books || [], quizzes: window.OrderingSettings?.quizzes || [], articles: window.OrderingSettings?.articles || [] }, calendarEvents: [] }; } const jsonStr = JSON.stringify(backupPayload, null, 2); const blob = new Blob([jsonStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `backup_${new Date().toISOString().split('T')[0]}_(${backupPayload.metadata.source}).json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this._log(`✅ تم تصدير النسخة الاحتياطية بنجاح`); return true; } catch (error) { console.error('خطأ تصدير النسخة الاحتياطية:', error); return false; } }, setupNetworkListeners() { if (this._networkListenersSetup) return; this._networkListenersSetup = true; if (window.ConnectionManager) { window.ConnectionManager.onStatusChange(async (isOnline) => { this._log(isOnline ? '🌐 تم استعادة الاتصال' : '📴 تم فقدان الاتصال'); // 🛠️ [v9.7 إصلاح]: إزالة طلبات الـ fetch من السحابي هنا، نكتفي ببث حدث المزامنة وترك Loader يتولى الاتصال window.dispatchEvent(new CustomEvent('systemNetworkSyncComplete', { detail: { isOnline, timestamp: Date.now() } })); }); } }, bindGlobalMethods() { window.savePinnedItems = this.savePinnedItems.bind(this); window.loadPinnedItems = this.loadPinnedItems.bind(this); window.togglePinItem = this.togglePinItem.bind(this); window.isItemPinned = this.isItemPinned.bind(this); window.getPinnedItems = this.getPinnedItems.bind(this); window.saveOrdering = this.saveOrdering.bind(this); window.loadOrdering = this.loadOrdering.bind(this); window.applyFullOrdering = this.applyFullOrdering.bind(this); window.hasPermission = this.hasPermissionByLevel.bind(this); window.hasPermissionByLevel = this.hasPermissionByLevel.bind(this); window.exportSystemBackup = this.exportSystemBackup.bind(this); window.canDeleteComments = this.canDeleteComments.bind(this); window.canDeleteSpecificComment = this.canDeleteSpecificComment.bind(this); window.isSystemOnline = this.isOnline.bind(this); window.getFirebaseDB = this.getDB.bind(this); window.retryInitFirebase = this.retryInitFirebase.bind(this); const defaultAppState = { currentUser: null, currentPage: 'home', currentAdminTab: 'books', currentDetailType: null, currentDetailId: null, currentRating: 0, allBooks: [], allQuizzes: [], allArticles: [], allComments: [], allUsers: [], allActivityLogs: [], siteSettings: {}, navigationSettings: {}, previousPage: 'home', activityFilters: { actionType: '', itemType: '', userId: '', dateFrom: '', dateTo: '' }, searchContext: { currentPage: 'home', currentAdminTab: null, searchableItems: [], searchResults: [], lastQuery: '', isSearchEnabled: false, isAdminSearch: false, allowCrossSearch: true } }; if (!window.AppState) { window.AppState = defaultAppState; } else { Object.keys(defaultAppState).forEach(key => { if (!(key in window.AppState)) { window.AppState[key] = defaultAppState[key]; } }); if (!window.AppState.activityFilters) window.AppState.activityFilters = defaultAppState.activityFilters; if (!window.AppState.searchContext) window.AppState.searchContext = defaultAppState.searchContext; } this._log('✅ Global methods bound and AppState merged (v9.7)'); }, initBridgesAndSync() { this._log('🚀 بدء تهيئة AppConfig...'); this.bindGlobalMethods(); this.initFirebase(); this.setupNetworkListeners(); Promise.allSettled([ this._loadPinnedFromLocal(), this._loadOrderingFromLocal() ]).then(() => { this._log('📊 تم تحميل الإعدادات المحلية بنجاح وضمان سلامة المصفوفات'); }).catch(e => { this._log('⚠️ تحميل الإعدادات المحلية:', e); }); this._log('✅ config.js جاهز v9.7'); window.dispatchEvent(new CustomEvent('appconfig-ready', { detail: { timestamp: Date.now(), version: '9.7' } })); } }; // بدء التهيئة الفورية الآمنة تزامناً مع الهيكل الهجين window.AppConfigContainer.initBridgesAndSync();