@ozdao/martyrs 0.2.536 → 0.2.537

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.
@@ -84,7 +84,7 @@ class ModuleRegistry {
84
84
  if (config.routes.some((route) => {
85
85
  if (typeof route === "string") {
86
86
  if (route === "/") {
87
- return normalizedPath === "/";
87
+ return true;
88
88
  }
89
89
  const normalizedRoute = route.replace(/\/$/, "");
90
90
  return normalizedPath === normalizedRoute || normalizedPath.startsWith(normalizedRoute + "/");
@@ -1 +1 @@
1
- {"version":3,"file":"module-registry.cjs","sources":["../../../../../../../src/modules/globals/views/classes/module-registry.js"],"sourcesContent":["// module-registry.js - централизованный регистр модулей\nimport { ref, readonly } from 'vue';\n\nexport class ModuleRegistry {\n constructor() {\n this.modules = new Map();\n this.loaders = new Map();\n this.initialized = new Map();\n this.dependencies = new Map();\n this.loadingPromises = new Map(); // Для предотвращения дублирования загрузки\n }\n\n // Регистрация модуля с ленивой загрузкой\n register(name, config) {\n this.loaders.set(name, {\n loader: config.loader,\n routes: config.routes || [],\n dependencies: config.dependencies || [],\n priority: config.priority || 'normal',\n preload: config.preload || false,\n critical: config.critical || false,\n });\n }\n\n // Загрузка модуля\n async load(name, context = {}) {\n // Если модуль уже загружен\n if (this.modules.has(name)) {\n return this.modules.get(name);\n }\n\n // Если модуль уже загружается, вернуть существующий промис\n if (this.loadingPromises.has(name)) {\n return this.loadingPromises.get(name);\n }\n\n const config = this.loaders.get(name);\n if (!config) {\n throw new Error(`Module ${name} not registered`);\n }\n\n // Создаем промис загрузки и сохраняем его\n const loadPromise = this._loadModule(name, context, config);\n this.loadingPromises.set(name, loadPromise);\n\n try {\n const module = await loadPromise;\n this.loadingPromises.delete(name);\n return module;\n } catch (error) {\n this.loadingPromises.delete(name);\n throw error;\n }\n }\n\n // Внутренний метод для загрузки модуля\n async _loadModule(name, context, config) {\n const loadStart = Date.now();\n\n // Загружаем зависимости\n if (config.dependencies.length > 0) {\n await Promise.all(\n config.dependencies.map(dep => this.load(dep, context))\n );\n }\n\n // Загружаем сам модуль\n const module = await config.loader();\n \n const mod = module.default || module;\n \n this.modules.set(name, mod);\n \n // Инициализируем если есть контекст\n if (context.app && !this.initialized.has(name)) {\n await this.initialize(name, context);\n }\n\n return mod;\n }\n\n // Инициализация модуля\n async initialize(name, { app, store, router, config }) {\n const module = this.modules.get(name);\n if (!module || this.initialized.has(name)) {\n return;\n }\n\n // Инициализируем зависимости\n const moduleConfig = this.loaders.get(name);\n if (moduleConfig.dependencies.length > 0) {\n for (const dep of moduleConfig.dependencies) {\n await this.initialize(dep, { app, store, router, config });\n }\n }\n\n // Инициализируем модуль\n // Поддерживаем оба варианта: module.initialize и module.default.initialize для обратной совместимости\n const initFunc = module.initialize || (module.default && module.default.initialize);\n if (initFunc) {\n await initFunc(app, store, router, config);\n }\n \n this.initialized.set(name, true);\n }\n\n // Получить модули для маршрута\n getModulesForRoute(path) {\n const modules = [];\n \n // Нормализуем путь - убираем trailing slash если это не корень\n const normalizedPath = path === '/' ? '/' : path.replace(/\\/$/, '');\n \n for (const [name, config] of this.loaders) {\n if (config.routes.some(route => {\n if (typeof route === 'string') {\n // Для корневого роута проверяем точное совпадение\n if (route === '/') {\n return normalizedPath === '/';\n }\n // Для остальных роутов проверяем начало пути\n // но учитываем границы сегментов (не /events должен матчить /event)\n const normalizedRoute = route.replace(/\\/$/, '');\n return normalizedPath === normalizedRoute || \n normalizedPath.startsWith(normalizedRoute + '/');\n }\n if (route instanceof RegExp) {\n return route.test(normalizedPath);\n }\n return false;\n })) {\n modules.push({ name, ...config });\n }\n }\n\n // Сортируем по приоритету\n return modules.sort((a, b) => {\n const priorities = { critical: 0, high: 1, normal: 2, low: 3 };\n return (priorities[a.priority] || 2) - (priorities[b.priority] || 2);\n });\n }\n \n // Получить критические модули для маршрута (для SSR)\n getCriticalModulesForRoute(path) {\n const allModules = this.getModulesForRoute(path);\n \n // Фильтруем только критические модули и модули с высоким приоритетом\n const criticalModules = allModules.filter(m => \n m.critical || m.priority === 'critical' || m.priority === 'high'\n );\n \n // Добавляем базовые модули, которые всегда критические\n const baseModules = ['globals', 'auth'];\n const moduleNames = new Set([...baseModules, ...criticalModules.map(m => m.name)]);\n \n // Добавляем зависимости критических модулей\n for (const moduleName of moduleNames) {\n const config = this.loaders.get(moduleName);\n if (config && config.dependencies) {\n for (const dep of config.dependencies) {\n moduleNames.add(dep);\n }\n }\n }\n \n return Array.from(moduleNames);\n }\n\n // Предзагрузка модулей\n async preloadModules(context) {\n const toPreload = [];\n \n for (const [name, config] of this.loaders) {\n if (config.preload || config.critical) {\n toPreload.push({ name, ...config });\n }\n }\n\n // Сортируем по приоритету\n toPreload.sort((a, b) => {\n if (a.critical && !b.critical) return -1;\n if (!a.critical && b.critical) return 1;\n const priorities = { critical: 0, high: 1, normal: 2, low: 3 };\n return (priorities[a.priority] || 2) - (priorities[b.priority] || 2);\n });\n\n // Загружаем критические синхронно\n const critical = toPreload.filter(m => m.critical);\n for (const module of critical) {\n await this.load(module.name, context);\n }\n\n // Остальные в фоне\n const normal = toPreload.filter(m => !m.critical);\n if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {\n window.requestIdleCallback(() => {\n normal.forEach(module => this.load(module.name, context));\n });\n } else {\n setTimeout(() => {\n normal.forEach(module => this.load(module.name, context));\n }, 100);\n }\n }\n\n}\n\n// Создаем глобальный регистр\nexport const moduleRegistry = new ModuleRegistry();\n\n// Утилита для использования в компонентах\nexport async function useModule(name) {\n return moduleRegistry.load(name);\n}\n\n// Composable для Vue\nexport function useModuleLoader() {\n const loading = ref(false);\n const error = ref(null);\n\n const loadModule = async (name) => {\n loading.value = true;\n error.value = null;\n \n try {\n const module = await moduleRegistry.load(name);\n return module;\n } catch (e) {\n error.value = e;\n throw e;\n } finally {\n loading.value = false;\n }\n };\n\n return {\n loadModule,\n loading: readonly(loading),\n error: readonly(error),\n };\n}"],"names":["module"],"mappings":"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"}
1
+ {"version":3,"file":"module-registry.cjs","sources":["../../../../../../../src/modules/globals/views/classes/module-registry.js"],"sourcesContent":["// module-registry.js - централизованный регистр модулей\nimport { ref, readonly } from 'vue';\n\nexport class ModuleRegistry {\n constructor() {\n this.modules = new Map();\n this.loaders = new Map();\n this.initialized = new Map();\n this.dependencies = new Map();\n this.loadingPromises = new Map(); // Для предотвращения дублирования загрузки\n }\n\n // Регистрация модуля с ленивой загрузкой\n register(name, config) {\n this.loaders.set(name, {\n loader: config.loader,\n routes: config.routes || [],\n dependencies: config.dependencies || [],\n priority: config.priority || 'normal',\n preload: config.preload || false,\n critical: config.critical || false,\n });\n }\n\n // Загрузка модуля\n async load(name, context = {}) {\n // Если модуль уже загружен\n if (this.modules.has(name)) {\n return this.modules.get(name);\n }\n\n // Если модуль уже загружается, вернуть существующий промис\n if (this.loadingPromises.has(name)) {\n return this.loadingPromises.get(name);\n }\n\n const config = this.loaders.get(name);\n if (!config) {\n throw new Error(`Module ${name} not registered`);\n }\n\n // Создаем промис загрузки и сохраняем его\n const loadPromise = this._loadModule(name, context, config);\n this.loadingPromises.set(name, loadPromise);\n\n try {\n const module = await loadPromise;\n this.loadingPromises.delete(name);\n return module;\n } catch (error) {\n this.loadingPromises.delete(name);\n throw error;\n }\n }\n\n // Внутренний метод для загрузки модуля\n async _loadModule(name, context, config) {\n const loadStart = Date.now();\n\n // Загружаем зависимости\n if (config.dependencies.length > 0) {\n await Promise.all(\n config.dependencies.map(dep => this.load(dep, context))\n );\n }\n\n // Загружаем сам модуль\n const module = await config.loader();\n \n const mod = module.default || module;\n \n this.modules.set(name, mod);\n \n // Инициализируем если есть контекст\n if (context.app && !this.initialized.has(name)) {\n await this.initialize(name, context);\n }\n\n return mod;\n }\n\n // Инициализация модуля\n async initialize(name, { app, store, router, config }) {\n const module = this.modules.get(name);\n if (!module || this.initialized.has(name)) {\n return;\n }\n\n // Инициализируем зависимости\n const moduleConfig = this.loaders.get(name);\n if (moduleConfig.dependencies.length > 0) {\n for (const dep of moduleConfig.dependencies) {\n await this.initialize(dep, { app, store, router, config });\n }\n }\n\n // Инициализируем модуль\n // Поддерживаем оба варианта: module.initialize и module.default.initialize для обратной совместимости\n const initFunc = module.initialize || (module.default && module.default.initialize);\n if (initFunc) {\n await initFunc(app, store, router, config);\n }\n \n this.initialized.set(name, true);\n }\n\n // Получить модули для маршрута\n getModulesForRoute(path) {\n const modules = [];\n \n // Нормализуем путь - убираем trailing slash если это не корень\n const normalizedPath = path === '/' ? '/' : path.replace(/\\/$/, '');\n \n for (const [name, config] of this.loaders) {\n if (config.routes.some(route => {\n if (typeof route === 'string') {\n // Для корневого роута матчим все пути\n if (route === '/') {\n return true;\n }\n // Для остальных роутов проверяем начало пути\n // но учитываем границы сегментов (не /events должен матчить /event)\n const normalizedRoute = route.replace(/\\/$/, '');\n return normalizedPath === normalizedRoute || \n normalizedPath.startsWith(normalizedRoute + '/');\n }\n if (route instanceof RegExp) {\n return route.test(normalizedPath);\n }\n return false;\n })) {\n modules.push({ name, ...config });\n }\n }\n\n // Сортируем по приоритету\n return modules.sort((a, b) => {\n const priorities = { critical: 0, high: 1, normal: 2, low: 3 };\n return (priorities[a.priority] || 2) - (priorities[b.priority] || 2);\n });\n }\n \n // Получить критические модули для маршрута (для SSR)\n getCriticalModulesForRoute(path) {\n const allModules = this.getModulesForRoute(path);\n \n // Фильтруем только критические модули и модули с высоким приоритетом\n const criticalModules = allModules.filter(m => \n m.critical || m.priority === 'critical' || m.priority === 'high'\n );\n \n // Добавляем базовые модули, которые всегда критические\n const baseModules = ['globals', 'auth'];\n const moduleNames = new Set([...baseModules, ...criticalModules.map(m => m.name)]);\n \n // Добавляем зависимости критических модулей\n for (const moduleName of moduleNames) {\n const config = this.loaders.get(moduleName);\n if (config && config.dependencies) {\n for (const dep of config.dependencies) {\n moduleNames.add(dep);\n }\n }\n }\n \n return Array.from(moduleNames);\n }\n\n // Предзагрузка модулей\n async preloadModules(context) {\n const toPreload = [];\n \n for (const [name, config] of this.loaders) {\n if (config.preload || config.critical) {\n toPreload.push({ name, ...config });\n }\n }\n\n // Сортируем по приоритету\n toPreload.sort((a, b) => {\n if (a.critical && !b.critical) return -1;\n if (!a.critical && b.critical) return 1;\n const priorities = { critical: 0, high: 1, normal: 2, low: 3 };\n return (priorities[a.priority] || 2) - (priorities[b.priority] || 2);\n });\n\n // Загружаем критические синхронно\n const critical = toPreload.filter(m => m.critical);\n for (const module of critical) {\n await this.load(module.name, context);\n }\n\n // Остальные в фоне\n const normal = toPreload.filter(m => !m.critical);\n if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {\n window.requestIdleCallback(() => {\n normal.forEach(module => this.load(module.name, context));\n });\n } else {\n setTimeout(() => {\n normal.forEach(module => this.load(module.name, context));\n }, 100);\n }\n }\n\n}\n\n// Создаем глобальный регистр\nexport const moduleRegistry = new ModuleRegistry();\n\n// Утилита для использования в компонентах\nexport async function useModule(name) {\n return moduleRegistry.load(name);\n}\n\n// Composable для Vue\nexport function useModuleLoader() {\n const loading = ref(false);\n const error = ref(null);\n\n const loadModule = async (name) => {\n loading.value = true;\n error.value = null;\n \n try {\n const module = await moduleRegistry.load(name);\n return module;\n } catch (e) {\n error.value = e;\n throw e;\n } finally {\n loading.value = false;\n }\n };\n\n return {\n loadModule,\n loading: readonly(loading),\n error: readonly(error),\n };\n}"],"names":["module"],"mappings":"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"}
@@ -82,7 +82,7 @@ class ModuleRegistry {
82
82
  if (config.routes.some((route) => {
83
83
  if (typeof route === "string") {
84
84
  if (route === "/") {
85
- return normalizedPath === "/";
85
+ return true;
86
86
  }
87
87
  const normalizedRoute = route.replace(/\/$/, "");
88
88
  return normalizedPath === normalizedRoute || normalizedPath.startsWith(normalizedRoute + "/");
@@ -1 +1 @@
1
- {"version":3,"file":"module-registry.js","sources":["../../../../../../../src/modules/globals/views/classes/module-registry.js"],"sourcesContent":["// module-registry.js - централизованный регистр модулей\nimport { ref, readonly } from 'vue';\n\nexport class ModuleRegistry {\n constructor() {\n this.modules = new Map();\n this.loaders = new Map();\n this.initialized = new Map();\n this.dependencies = new Map();\n this.loadingPromises = new Map(); // Для предотвращения дублирования загрузки\n }\n\n // Регистрация модуля с ленивой загрузкой\n register(name, config) {\n this.loaders.set(name, {\n loader: config.loader,\n routes: config.routes || [],\n dependencies: config.dependencies || [],\n priority: config.priority || 'normal',\n preload: config.preload || false,\n critical: config.critical || false,\n });\n }\n\n // Загрузка модуля\n async load(name, context = {}) {\n // Если модуль уже загружен\n if (this.modules.has(name)) {\n return this.modules.get(name);\n }\n\n // Если модуль уже загружается, вернуть существующий промис\n if (this.loadingPromises.has(name)) {\n return this.loadingPromises.get(name);\n }\n\n const config = this.loaders.get(name);\n if (!config) {\n throw new Error(`Module ${name} not registered`);\n }\n\n // Создаем промис загрузки и сохраняем его\n const loadPromise = this._loadModule(name, context, config);\n this.loadingPromises.set(name, loadPromise);\n\n try {\n const module = await loadPromise;\n this.loadingPromises.delete(name);\n return module;\n } catch (error) {\n this.loadingPromises.delete(name);\n throw error;\n }\n }\n\n // Внутренний метод для загрузки модуля\n async _loadModule(name, context, config) {\n const loadStart = Date.now();\n\n // Загружаем зависимости\n if (config.dependencies.length > 0) {\n await Promise.all(\n config.dependencies.map(dep => this.load(dep, context))\n );\n }\n\n // Загружаем сам модуль\n const module = await config.loader();\n \n const mod = module.default || module;\n \n this.modules.set(name, mod);\n \n // Инициализируем если есть контекст\n if (context.app && !this.initialized.has(name)) {\n await this.initialize(name, context);\n }\n\n return mod;\n }\n\n // Инициализация модуля\n async initialize(name, { app, store, router, config }) {\n const module = this.modules.get(name);\n if (!module || this.initialized.has(name)) {\n return;\n }\n\n // Инициализируем зависимости\n const moduleConfig = this.loaders.get(name);\n if (moduleConfig.dependencies.length > 0) {\n for (const dep of moduleConfig.dependencies) {\n await this.initialize(dep, { app, store, router, config });\n }\n }\n\n // Инициализируем модуль\n // Поддерживаем оба варианта: module.initialize и module.default.initialize для обратной совместимости\n const initFunc = module.initialize || (module.default && module.default.initialize);\n if (initFunc) {\n await initFunc(app, store, router, config);\n }\n \n this.initialized.set(name, true);\n }\n\n // Получить модули для маршрута\n getModulesForRoute(path) {\n const modules = [];\n \n // Нормализуем путь - убираем trailing slash если это не корень\n const normalizedPath = path === '/' ? '/' : path.replace(/\\/$/, '');\n \n for (const [name, config] of this.loaders) {\n if (config.routes.some(route => {\n if (typeof route === 'string') {\n // Для корневого роута проверяем точное совпадение\n if (route === '/') {\n return normalizedPath === '/';\n }\n // Для остальных роутов проверяем начало пути\n // но учитываем границы сегментов (не /events должен матчить /event)\n const normalizedRoute = route.replace(/\\/$/, '');\n return normalizedPath === normalizedRoute || \n normalizedPath.startsWith(normalizedRoute + '/');\n }\n if (route instanceof RegExp) {\n return route.test(normalizedPath);\n }\n return false;\n })) {\n modules.push({ name, ...config });\n }\n }\n\n // Сортируем по приоритету\n return modules.sort((a, b) => {\n const priorities = { critical: 0, high: 1, normal: 2, low: 3 };\n return (priorities[a.priority] || 2) - (priorities[b.priority] || 2);\n });\n }\n \n // Получить критические модули для маршрута (для SSR)\n getCriticalModulesForRoute(path) {\n const allModules = this.getModulesForRoute(path);\n \n // Фильтруем только критические модули и модули с высоким приоритетом\n const criticalModules = allModules.filter(m => \n m.critical || m.priority === 'critical' || m.priority === 'high'\n );\n \n // Добавляем базовые модули, которые всегда критические\n const baseModules = ['globals', 'auth'];\n const moduleNames = new Set([...baseModules, ...criticalModules.map(m => m.name)]);\n \n // Добавляем зависимости критических модулей\n for (const moduleName of moduleNames) {\n const config = this.loaders.get(moduleName);\n if (config && config.dependencies) {\n for (const dep of config.dependencies) {\n moduleNames.add(dep);\n }\n }\n }\n \n return Array.from(moduleNames);\n }\n\n // Предзагрузка модулей\n async preloadModules(context) {\n const toPreload = [];\n \n for (const [name, config] of this.loaders) {\n if (config.preload || config.critical) {\n toPreload.push({ name, ...config });\n }\n }\n\n // Сортируем по приоритету\n toPreload.sort((a, b) => {\n if (a.critical && !b.critical) return -1;\n if (!a.critical && b.critical) return 1;\n const priorities = { critical: 0, high: 1, normal: 2, low: 3 };\n return (priorities[a.priority] || 2) - (priorities[b.priority] || 2);\n });\n\n // Загружаем критические синхронно\n const critical = toPreload.filter(m => m.critical);\n for (const module of critical) {\n await this.load(module.name, context);\n }\n\n // Остальные в фоне\n const normal = toPreload.filter(m => !m.critical);\n if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {\n window.requestIdleCallback(() => {\n normal.forEach(module => this.load(module.name, context));\n });\n } else {\n setTimeout(() => {\n normal.forEach(module => this.load(module.name, context));\n }, 100);\n }\n }\n\n}\n\n// Создаем глобальный регистр\nexport const moduleRegistry = new ModuleRegistry();\n\n// Утилита для использования в компонентах\nexport async function useModule(name) {\n return moduleRegistry.load(name);\n}\n\n// Composable для Vue\nexport function useModuleLoader() {\n const loading = ref(false);\n const error = ref(null);\n\n const loadModule = async (name) => {\n loading.value = true;\n error.value = null;\n \n try {\n const module = await moduleRegistry.load(name);\n return module;\n } catch (e) {\n error.value = e;\n throw e;\n } finally {\n loading.value = false;\n }\n };\n\n return {\n loadModule,\n loading: readonly(loading),\n error: readonly(error),\n };\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"module-registry.js","sources":["../../../../../../../src/modules/globals/views/classes/module-registry.js"],"sourcesContent":["// module-registry.js - централизованный регистр модулей\nimport { ref, readonly } from 'vue';\n\nexport class ModuleRegistry {\n constructor() {\n this.modules = new Map();\n this.loaders = new Map();\n this.initialized = new Map();\n this.dependencies = new Map();\n this.loadingPromises = new Map(); // Для предотвращения дублирования загрузки\n }\n\n // Регистрация модуля с ленивой загрузкой\n register(name, config) {\n this.loaders.set(name, {\n loader: config.loader,\n routes: config.routes || [],\n dependencies: config.dependencies || [],\n priority: config.priority || 'normal',\n preload: config.preload || false,\n critical: config.critical || false,\n });\n }\n\n // Загрузка модуля\n async load(name, context = {}) {\n // Если модуль уже загружен\n if (this.modules.has(name)) {\n return this.modules.get(name);\n }\n\n // Если модуль уже загружается, вернуть существующий промис\n if (this.loadingPromises.has(name)) {\n return this.loadingPromises.get(name);\n }\n\n const config = this.loaders.get(name);\n if (!config) {\n throw new Error(`Module ${name} not registered`);\n }\n\n // Создаем промис загрузки и сохраняем его\n const loadPromise = this._loadModule(name, context, config);\n this.loadingPromises.set(name, loadPromise);\n\n try {\n const module = await loadPromise;\n this.loadingPromises.delete(name);\n return module;\n } catch (error) {\n this.loadingPromises.delete(name);\n throw error;\n }\n }\n\n // Внутренний метод для загрузки модуля\n async _loadModule(name, context, config) {\n const loadStart = Date.now();\n\n // Загружаем зависимости\n if (config.dependencies.length > 0) {\n await Promise.all(\n config.dependencies.map(dep => this.load(dep, context))\n );\n }\n\n // Загружаем сам модуль\n const module = await config.loader();\n \n const mod = module.default || module;\n \n this.modules.set(name, mod);\n \n // Инициализируем если есть контекст\n if (context.app && !this.initialized.has(name)) {\n await this.initialize(name, context);\n }\n\n return mod;\n }\n\n // Инициализация модуля\n async initialize(name, { app, store, router, config }) {\n const module = this.modules.get(name);\n if (!module || this.initialized.has(name)) {\n return;\n }\n\n // Инициализируем зависимости\n const moduleConfig = this.loaders.get(name);\n if (moduleConfig.dependencies.length > 0) {\n for (const dep of moduleConfig.dependencies) {\n await this.initialize(dep, { app, store, router, config });\n }\n }\n\n // Инициализируем модуль\n // Поддерживаем оба варианта: module.initialize и module.default.initialize для обратной совместимости\n const initFunc = module.initialize || (module.default && module.default.initialize);\n if (initFunc) {\n await initFunc(app, store, router, config);\n }\n \n this.initialized.set(name, true);\n }\n\n // Получить модули для маршрута\n getModulesForRoute(path) {\n const modules = [];\n \n // Нормализуем путь - убираем trailing slash если это не корень\n const normalizedPath = path === '/' ? '/' : path.replace(/\\/$/, '');\n \n for (const [name, config] of this.loaders) {\n if (config.routes.some(route => {\n if (typeof route === 'string') {\n // Для корневого роута матчим все пути\n if (route === '/') {\n return true;\n }\n // Для остальных роутов проверяем начало пути\n // но учитываем границы сегментов (не /events должен матчить /event)\n const normalizedRoute = route.replace(/\\/$/, '');\n return normalizedPath === normalizedRoute || \n normalizedPath.startsWith(normalizedRoute + '/');\n }\n if (route instanceof RegExp) {\n return route.test(normalizedPath);\n }\n return false;\n })) {\n modules.push({ name, ...config });\n }\n }\n\n // Сортируем по приоритету\n return modules.sort((a, b) => {\n const priorities = { critical: 0, high: 1, normal: 2, low: 3 };\n return (priorities[a.priority] || 2) - (priorities[b.priority] || 2);\n });\n }\n \n // Получить критические модули для маршрута (для SSR)\n getCriticalModulesForRoute(path) {\n const allModules = this.getModulesForRoute(path);\n \n // Фильтруем только критические модули и модули с высоким приоритетом\n const criticalModules = allModules.filter(m => \n m.critical || m.priority === 'critical' || m.priority === 'high'\n );\n \n // Добавляем базовые модули, которые всегда критические\n const baseModules = ['globals', 'auth'];\n const moduleNames = new Set([...baseModules, ...criticalModules.map(m => m.name)]);\n \n // Добавляем зависимости критических модулей\n for (const moduleName of moduleNames) {\n const config = this.loaders.get(moduleName);\n if (config && config.dependencies) {\n for (const dep of config.dependencies) {\n moduleNames.add(dep);\n }\n }\n }\n \n return Array.from(moduleNames);\n }\n\n // Предзагрузка модулей\n async preloadModules(context) {\n const toPreload = [];\n \n for (const [name, config] of this.loaders) {\n if (config.preload || config.critical) {\n toPreload.push({ name, ...config });\n }\n }\n\n // Сортируем по приоритету\n toPreload.sort((a, b) => {\n if (a.critical && !b.critical) return -1;\n if (!a.critical && b.critical) return 1;\n const priorities = { critical: 0, high: 1, normal: 2, low: 3 };\n return (priorities[a.priority] || 2) - (priorities[b.priority] || 2);\n });\n\n // Загружаем критические синхронно\n const critical = toPreload.filter(m => m.critical);\n for (const module of critical) {\n await this.load(module.name, context);\n }\n\n // Остальные в фоне\n const normal = toPreload.filter(m => !m.critical);\n if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {\n window.requestIdleCallback(() => {\n normal.forEach(module => this.load(module.name, context));\n });\n } else {\n setTimeout(() => {\n normal.forEach(module => this.load(module.name, context));\n }, 100);\n }\n }\n\n}\n\n// Создаем глобальный регистр\nexport const moduleRegistry = new ModuleRegistry();\n\n// Утилита для использования в компонентах\nexport async function useModule(name) {\n return moduleRegistry.load(name);\n}\n\n// Composable для Vue\nexport function useModuleLoader() {\n const loading = ref(false);\n const error = ref(null);\n\n const loadModule = async (name) => {\n loading.value = true;\n error.value = null;\n \n try {\n const module = await moduleRegistry.load(name);\n return module;\n } catch (e) {\n error.value = e;\n throw e;\n } finally {\n loading.value = false;\n }\n };\n\n return {\n loadModule,\n loading: readonly(loading),\n error: readonly(error),\n };\n}"],"names":[],"mappings":"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"}
@@ -43,7 +43,16 @@ const _sfc_main = {
43
43
  setup(__props) {
44
44
  const props = __props;
45
45
  vueRouter.useRouter();
46
- const { notifications, unreadCount, loading, markAllAsRead, getNotifications } = vue.inject("useNotifications")();
46
+ const useNotifications = vue.inject("useNotifications");
47
+ const { notifications, unreadCount, loading, markAllAsRead, getNotifications } = useNotifications ? useNotifications() : {
48
+ notifications: vue.ref([]),
49
+ unreadCount: vue.ref(0),
50
+ loading: vue.ref(false),
51
+ markAllAsRead: () => {
52
+ },
53
+ getNotifications: () => {
54
+ }
55
+ };
47
56
  const isOpen = vue.ref(false);
48
57
  const recentNotifications = vue.computed(() => {
49
58
  return notifications.value.slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).slice(0, props.maxNotifications);
@@ -128,6 +137,6 @@ const _sfc_main = {
128
137
  };
129
138
  }
130
139
  };
131
- const NotificationBadge = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-c9d03610"]]);
140
+ const NotificationBadge = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["__scopeId", "data-v-f08c11b0"]]);
132
141
  exports.default = NotificationBadge;
133
142
  //# sourceMappingURL=NotificationBadge.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationBadge.vue.cjs","sources":["../../../../../../../src/modules/notifications/components/elements/NotificationBadge.vue"],"sourcesContent":["<template>\n <div class=\"notification-badge-container\">\n <button \n class=\"i-semi notification-button\"\n @click=\"toggleNotifications\"\n :aria-label=\"unreadCount > 0 ? `${unreadCount} unread notifications` : 'No unread notifications'\"\n >\n <IconBell class=\"notification-icon i-medium\" :fill=\"fill\"/>\n <div \n v-if=\"unreadCount > 0\" \n class=\"button-counter flex flex-center\"\n >\n <span>{{ unreadCount > 99 ? '99+' : unreadCount }}</span>\n </div>\n </button>\n \n <Popup\n title=\"Notifications\"\n @close-popup=\"closeNotifications\"\n :isPopupOpen=\"isOpen\"\n align=\"center right\"\n class=\"bg-white h-min-100 w-100 w-max-30r pd-medium\"\n >\n <div class=\"cols-1 gap-thin o-y-scroll\">\n <div v-if=\"loading\" class=\"notifications-loading\">\n Loading...\n </div>\n \n <div v-else-if=\"notifications.length === 0\" class=\"notifications-empty\">\n No notifications\n </div>\n \n <div v-else class=\"flex-column flex gap-thin\">\n <notification-item \n v-for=\"notification in recentNotifications\" \n :key=\"notification._id\" \n :notification=\"notification\"\n @click=\"handleNotificationClick(notification)\"\n />\n\n <div class=\"flex-nowrap flex gap-thin\">\n <button \n v-if=\"unreadCount > 0\"\n class=\"w-100 bg-second t-white radius-small button\" \n @click=\"markAllAsRead\"\n >\n Mark all read\n </button>\n\n <router-link class=\"w-100 bg-black t-white radius-small button\" to=\"/notifications\" @click=\"closeNotifications\">\n View all \n </router-link>\n </div>\n\n </div>\n\n </div>\n </Popup>\n </div>\n</template>\n\n<script setup>\nimport { ref, computed, onMounted, inject } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport Popup from '@martyrs/src/components/Popup/Popup.vue';\n\nimport NotificationItem from '../blocks/NotificationItem.vue';\nimport * as auth from '@martyrs/src/modules/auth/views/store/auth.js';\nimport IconBell from '@martyrs/src/modules/icons/entities/IconBell.vue';\n\nconst props = defineProps({\n maxNotifications: {\n type: Number,\n default: 10\n },\n fill: {\n type: String,\n default: 'rgb(var(--white))'\n }\n});\n\n// Get router and notification functionality\nconst router = useRouter();\nconst { notifications, unreadCount, loading, markAllAsRead, getNotifications } = inject('useNotifications')();\n\n// Local state\nconst isOpen = ref(false);\n\n// Computed properties\nconst recentNotifications = computed(() => {\n return notifications.value\n .slice()\n .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))\n .slice(0, props.maxNotifications);\n});\n\n// Methods\nconst toggleNotifications = () => {\n isOpen.value = !isOpen.value;\n};\n\nconst closeNotifications = () => {\n isOpen.value = false;\n};\n\nconst handleNotificationClick = (notification) => {\n isOpen.value = false;\n \n if (notification._id) {\n // Handle in store\n actions.handleNotificationAction({\n notificationId: notification._id,\n ...notification.metadata\n });\n }\n};\n\n// Lifecycle hooks\nonMounted(() => {\n // Load notifications when component mounts\n const userId = auth.state.user._id;\n if (userId) {\n getNotifications(userId);\n }\n});\n</script>\n\n<style scoped>\n.notification-badge-container {\n position: relative;\n display: inline-block;\n}\n\n.notification-button {\n background: none;\n border: none;\n cursor: pointer;\n position: relative;\n font-size: 1.2rem;\n}\n\n.notification-icon {\n font-size: 1.4rem;\n}\n\n.button-counter {\n position: absolute;\n right: -8px;\n bottom: -8px;\n background: rgb(var(--fourth));\n color: rgb(var(--white));\n height: 16px;\n border-radius: 16px;\n width: 16px;\n font-weight: 500;\n text-align: center;\n line-height: 16px;\n font-size: 10px;\n}\n\n\n.notifications-loading,\n.notifications-empty {\n padding: 24px;\n text-align: center;\n color: rgb(var(--text-light));\n}\n</style>"],"names":["useRouter","inject","ref","computed","onMounted","auth.state"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,UAAM,QAAQ;AAYCA,cAAAA,UAAS;AACxB,UAAM,EAAE,eAAe,aAAa,SAAS,eAAe,qBAAqBC,IAAAA,OAAO,kBAAkB,EAAC;AAG3G,UAAM,SAASC,IAAAA,IAAI,KAAK;AAGxB,UAAM,sBAAsBC,IAAAA,SAAS,MAAM;AACzC,aAAO,cAAc,MAClB,MAAK,EACL,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,IAAI,IAAI,KAAK,EAAE,SAAS,CAAC,EAC5D,MAAM,GAAG,MAAM,gBAAgB;AAAA,IACpC,CAAC;AAGD,UAAM,sBAAsB,MAAM;AAChC,aAAO,QAAQ,CAAC,OAAO;AAAA,IACzB;AAEA,UAAM,qBAAqB,MAAM;AAC/B,aAAO,QAAQ;AAAA,IACjB;AAEA,UAAM,0BAA0B,CAAC,iBAAiB;AAChD,aAAO,QAAQ;AAEf,UAAI,aAAa,KAAK;AAEpB,gBAAQ,yBAAyB;AAAA,UAC/B,gBAAgB,aAAa;AAAA,UAC7B,GAAG,aAAa;AAAA,QACtB,CAAK;AAAA,MACH;AAAA,IACF;AAGAC,QAAAA,UAAU,MAAM;AAEd,YAAM,SAASC,WAAW,KAAK;AAC/B,UAAI,QAAQ;AACV,yBAAiB,MAAM;AAAA,MACzB;AAAA,IACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"NotificationBadge.vue.cjs","sources":["../../../../../../../src/modules/notifications/components/elements/NotificationBadge.vue"],"sourcesContent":["<template>\n <div class=\"notification-badge-container\">\n <button \n class=\"i-semi notification-button\"\n @click=\"toggleNotifications\"\n :aria-label=\"unreadCount > 0 ? `${unreadCount} unread notifications` : 'No unread notifications'\"\n >\n <IconBell class=\"notification-icon i-medium\" :fill=\"fill\"/>\n <div \n v-if=\"unreadCount > 0\" \n class=\"button-counter flex flex-center\"\n >\n <span>{{ unreadCount > 99 ? '99+' : unreadCount }}</span>\n </div>\n </button>\n \n <Popup\n title=\"Notifications\"\n @close-popup=\"closeNotifications\"\n :isPopupOpen=\"isOpen\"\n align=\"center right\"\n class=\"bg-white h-min-100 w-100 w-max-30r pd-medium\"\n >\n <div class=\"cols-1 gap-thin o-y-scroll\">\n <div v-if=\"loading\" class=\"notifications-loading\">\n Loading...\n </div>\n \n <div v-else-if=\"notifications.length === 0\" class=\"notifications-empty\">\n No notifications\n </div>\n \n <div v-else class=\"flex-column flex gap-thin\">\n <notification-item \n v-for=\"notification in recentNotifications\" \n :key=\"notification._id\" \n :notification=\"notification\"\n @click=\"handleNotificationClick(notification)\"\n />\n\n <div class=\"flex-nowrap flex gap-thin\">\n <button \n v-if=\"unreadCount > 0\"\n class=\"w-100 bg-second t-white radius-small button\" \n @click=\"markAllAsRead\"\n >\n Mark all read\n </button>\n\n <router-link class=\"w-100 bg-black t-white radius-small button\" to=\"/notifications\" @click=\"closeNotifications\">\n View all \n </router-link>\n </div>\n\n </div>\n\n </div>\n </Popup>\n </div>\n</template>\n\n<script setup>\nimport { ref, computed, onMounted, inject } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport Popup from '@martyrs/src/components/Popup/Popup.vue';\n\nimport NotificationItem from '../blocks/NotificationItem.vue';\nimport * as auth from '@martyrs/src/modules/auth/views/store/auth.js';\nimport IconBell from '@martyrs/src/modules/icons/entities/IconBell.vue';\n\nconst props = defineProps({\n maxNotifications: {\n type: Number,\n default: 10\n },\n fill: {\n type: String,\n default: 'rgb(var(--white))'\n }\n});\n\n// Get router and notification functionality\nconst router = useRouter();\n\n// Check if notifications module is loaded\nconst useNotifications = inject('useNotifications');\n\n// Provide fallback values if module is not loaded\nconst { notifications, unreadCount, loading, markAllAsRead, getNotifications } = useNotifications ? useNotifications() : {\n notifications: ref([]),\n unreadCount: ref(0),\n loading: ref(false),\n markAllAsRead: () => {},\n getNotifications: () => {}\n};\n\n// Local state\nconst isOpen = ref(false);\n\n// Computed properties\nconst recentNotifications = computed(() => {\n return notifications.value\n .slice()\n .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))\n .slice(0, props.maxNotifications);\n});\n\n// Methods\nconst toggleNotifications = () => {\n isOpen.value = !isOpen.value;\n};\n\nconst closeNotifications = () => {\n isOpen.value = false;\n};\n\nconst handleNotificationClick = (notification) => {\n isOpen.value = false;\n \n if (notification._id) {\n // Handle in store\n actions.handleNotificationAction({\n notificationId: notification._id,\n ...notification.metadata\n });\n }\n};\n\n// Lifecycle hooks\nonMounted(() => {\n // Load notifications when component mounts\n const userId = auth.state.user._id;\n if (userId) {\n getNotifications(userId);\n }\n});\n</script>\n\n<style scoped>\n.notification-badge-container {\n position: relative;\n display: inline-block;\n}\n\n.notification-button {\n background: none;\n border: none;\n cursor: pointer;\n position: relative;\n font-size: 1.2rem;\n}\n\n.notification-icon {\n font-size: 1.4rem;\n}\n\n.button-counter {\n position: absolute;\n right: -8px;\n bottom: -8px;\n background: rgb(var(--fourth));\n color: rgb(var(--white));\n height: 16px;\n border-radius: 16px;\n width: 16px;\n font-weight: 500;\n text-align: center;\n line-height: 16px;\n font-size: 10px;\n}\n\n\n.notifications-loading,\n.notifications-empty {\n padding: 24px;\n text-align: center;\n color: rgb(var(--text-light));\n}\n</style>"],"names":["useRouter","inject","ref","computed","onMounted","auth.state"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,UAAM,QAAQ;AAYCA,cAAAA,UAAS;AAGxB,UAAM,mBAAmBC,IAAAA,OAAO,kBAAkB;AAGlD,UAAM,EAAE,eAAe,aAAa,SAAS,eAAe,qBAAqB,mBAAmB,qBAAqB;AAAA,MACvH,eAAeC,IAAAA,IAAI,EAAE;AAAA,MACrB,aAAaA,IAAAA,IAAI,CAAC;AAAA,MAClB,SAASA,IAAAA,IAAI,KAAK;AAAA,MAClB,eAAe,MAAM;AAAA,MAAC;AAAA,MACtB,kBAAkB,MAAM;AAAA,MAAC;AAAA,IAC3B;AAGA,UAAM,SAASA,IAAAA,IAAI,KAAK;AAGxB,UAAM,sBAAsBC,IAAAA,SAAS,MAAM;AACzC,aAAO,cAAc,MAClB,MAAK,EACL,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,IAAI,IAAI,KAAK,EAAE,SAAS,CAAC,EAC5D,MAAM,GAAG,MAAM,gBAAgB;AAAA,IACpC,CAAC;AAGD,UAAM,sBAAsB,MAAM;AAChC,aAAO,QAAQ,CAAC,OAAO;AAAA,IACzB;AAEA,UAAM,qBAAqB,MAAM;AAC/B,aAAO,QAAQ;AAAA,IACjB;AAEA,UAAM,0BAA0B,CAAC,iBAAiB;AAChD,aAAO,QAAQ;AAEf,UAAI,aAAa,KAAK;AAEpB,gBAAQ,yBAAyB;AAAA,UAC/B,gBAAgB,aAAa;AAAA,UAC7B,GAAG,aAAa;AAAA,QACtB,CAAK;AAAA,MACH;AAAA,IACF;AAGAC,QAAAA,UAAU,MAAM;AAEd,YAAM,SAASC,WAAW,KAAK;AAC/B,UAAI,QAAQ;AACV,yBAAiB,MAAM;AAAA,MACzB;AAAA,IACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -41,7 +41,16 @@ const _sfc_main = {
41
41
  setup(__props) {
42
42
  const props = __props;
43
43
  useRouter();
44
- const { notifications, unreadCount, loading, markAllAsRead, getNotifications } = inject("useNotifications")();
44
+ const useNotifications = inject("useNotifications");
45
+ const { notifications, unreadCount, loading, markAllAsRead, getNotifications } = useNotifications ? useNotifications() : {
46
+ notifications: ref([]),
47
+ unreadCount: ref(0),
48
+ loading: ref(false),
49
+ markAllAsRead: () => {
50
+ },
51
+ getNotifications: () => {
52
+ }
53
+ };
45
54
  const isOpen = ref(false);
46
55
  const recentNotifications = computed(() => {
47
56
  return notifications.value.slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).slice(0, props.maxNotifications);
@@ -126,7 +135,7 @@ const _sfc_main = {
126
135
  };
127
136
  }
128
137
  };
129
- const NotificationBadge = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-c9d03610"]]);
138
+ const NotificationBadge = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-f08c11b0"]]);
130
139
  export {
131
140
  NotificationBadge as default
132
141
  };
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationBadge.vue.js","sources":["../../../../../../../src/modules/notifications/components/elements/NotificationBadge.vue"],"sourcesContent":["<template>\n <div class=\"notification-badge-container\">\n <button \n class=\"i-semi notification-button\"\n @click=\"toggleNotifications\"\n :aria-label=\"unreadCount > 0 ? `${unreadCount} unread notifications` : 'No unread notifications'\"\n >\n <IconBell class=\"notification-icon i-medium\" :fill=\"fill\"/>\n <div \n v-if=\"unreadCount > 0\" \n class=\"button-counter flex flex-center\"\n >\n <span>{{ unreadCount > 99 ? '99+' : unreadCount }}</span>\n </div>\n </button>\n \n <Popup\n title=\"Notifications\"\n @close-popup=\"closeNotifications\"\n :isPopupOpen=\"isOpen\"\n align=\"center right\"\n class=\"bg-white h-min-100 w-100 w-max-30r pd-medium\"\n >\n <div class=\"cols-1 gap-thin o-y-scroll\">\n <div v-if=\"loading\" class=\"notifications-loading\">\n Loading...\n </div>\n \n <div v-else-if=\"notifications.length === 0\" class=\"notifications-empty\">\n No notifications\n </div>\n \n <div v-else class=\"flex-column flex gap-thin\">\n <notification-item \n v-for=\"notification in recentNotifications\" \n :key=\"notification._id\" \n :notification=\"notification\"\n @click=\"handleNotificationClick(notification)\"\n />\n\n <div class=\"flex-nowrap flex gap-thin\">\n <button \n v-if=\"unreadCount > 0\"\n class=\"w-100 bg-second t-white radius-small button\" \n @click=\"markAllAsRead\"\n >\n Mark all read\n </button>\n\n <router-link class=\"w-100 bg-black t-white radius-small button\" to=\"/notifications\" @click=\"closeNotifications\">\n View all \n </router-link>\n </div>\n\n </div>\n\n </div>\n </Popup>\n </div>\n</template>\n\n<script setup>\nimport { ref, computed, onMounted, inject } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport Popup from '@martyrs/src/components/Popup/Popup.vue';\n\nimport NotificationItem from '../blocks/NotificationItem.vue';\nimport * as auth from '@martyrs/src/modules/auth/views/store/auth.js';\nimport IconBell from '@martyrs/src/modules/icons/entities/IconBell.vue';\n\nconst props = defineProps({\n maxNotifications: {\n type: Number,\n default: 10\n },\n fill: {\n type: String,\n default: 'rgb(var(--white))'\n }\n});\n\n// Get router and notification functionality\nconst router = useRouter();\nconst { notifications, unreadCount, loading, markAllAsRead, getNotifications } = inject('useNotifications')();\n\n// Local state\nconst isOpen = ref(false);\n\n// Computed properties\nconst recentNotifications = computed(() => {\n return notifications.value\n .slice()\n .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))\n .slice(0, props.maxNotifications);\n});\n\n// Methods\nconst toggleNotifications = () => {\n isOpen.value = !isOpen.value;\n};\n\nconst closeNotifications = () => {\n isOpen.value = false;\n};\n\nconst handleNotificationClick = (notification) => {\n isOpen.value = false;\n \n if (notification._id) {\n // Handle in store\n actions.handleNotificationAction({\n notificationId: notification._id,\n ...notification.metadata\n });\n }\n};\n\n// Lifecycle hooks\nonMounted(() => {\n // Load notifications when component mounts\n const userId = auth.state.user._id;\n if (userId) {\n getNotifications(userId);\n }\n});\n</script>\n\n<style scoped>\n.notification-badge-container {\n position: relative;\n display: inline-block;\n}\n\n.notification-button {\n background: none;\n border: none;\n cursor: pointer;\n position: relative;\n font-size: 1.2rem;\n}\n\n.notification-icon {\n font-size: 1.4rem;\n}\n\n.button-counter {\n position: absolute;\n right: -8px;\n bottom: -8px;\n background: rgb(var(--fourth));\n color: rgb(var(--white));\n height: 16px;\n border-radius: 16px;\n width: 16px;\n font-weight: 500;\n text-align: center;\n line-height: 16px;\n font-size: 10px;\n}\n\n\n.notifications-loading,\n.notifications-empty {\n padding: 24px;\n text-align: center;\n color: rgb(var(--text-light));\n}\n</style>"],"names":["auth.state"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,UAAM,QAAQ;AAYC,cAAS;AACxB,UAAM,EAAE,eAAe,aAAa,SAAS,eAAe,qBAAqB,OAAO,kBAAkB,EAAC;AAG3G,UAAM,SAAS,IAAI,KAAK;AAGxB,UAAM,sBAAsB,SAAS,MAAM;AACzC,aAAO,cAAc,MAClB,MAAK,EACL,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,IAAI,IAAI,KAAK,EAAE,SAAS,CAAC,EAC5D,MAAM,GAAG,MAAM,gBAAgB;AAAA,IACpC,CAAC;AAGD,UAAM,sBAAsB,MAAM;AAChC,aAAO,QAAQ,CAAC,OAAO;AAAA,IACzB;AAEA,UAAM,qBAAqB,MAAM;AAC/B,aAAO,QAAQ;AAAA,IACjB;AAEA,UAAM,0BAA0B,CAAC,iBAAiB;AAChD,aAAO,QAAQ;AAEf,UAAI,aAAa,KAAK;AAEpB,gBAAQ,yBAAyB;AAAA,UAC/B,gBAAgB,aAAa;AAAA,UAC7B,GAAG,aAAa;AAAA,QACtB,CAAK;AAAA,MACH;AAAA,IACF;AAGA,cAAU,MAAM;AAEd,YAAM,SAASA,MAAW,KAAK;AAC/B,UAAI,QAAQ;AACV,yBAAiB,MAAM;AAAA,MACzB;AAAA,IACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"NotificationBadge.vue.js","sources":["../../../../../../../src/modules/notifications/components/elements/NotificationBadge.vue"],"sourcesContent":["<template>\n <div class=\"notification-badge-container\">\n <button \n class=\"i-semi notification-button\"\n @click=\"toggleNotifications\"\n :aria-label=\"unreadCount > 0 ? `${unreadCount} unread notifications` : 'No unread notifications'\"\n >\n <IconBell class=\"notification-icon i-medium\" :fill=\"fill\"/>\n <div \n v-if=\"unreadCount > 0\" \n class=\"button-counter flex flex-center\"\n >\n <span>{{ unreadCount > 99 ? '99+' : unreadCount }}</span>\n </div>\n </button>\n \n <Popup\n title=\"Notifications\"\n @close-popup=\"closeNotifications\"\n :isPopupOpen=\"isOpen\"\n align=\"center right\"\n class=\"bg-white h-min-100 w-100 w-max-30r pd-medium\"\n >\n <div class=\"cols-1 gap-thin o-y-scroll\">\n <div v-if=\"loading\" class=\"notifications-loading\">\n Loading...\n </div>\n \n <div v-else-if=\"notifications.length === 0\" class=\"notifications-empty\">\n No notifications\n </div>\n \n <div v-else class=\"flex-column flex gap-thin\">\n <notification-item \n v-for=\"notification in recentNotifications\" \n :key=\"notification._id\" \n :notification=\"notification\"\n @click=\"handleNotificationClick(notification)\"\n />\n\n <div class=\"flex-nowrap flex gap-thin\">\n <button \n v-if=\"unreadCount > 0\"\n class=\"w-100 bg-second t-white radius-small button\" \n @click=\"markAllAsRead\"\n >\n Mark all read\n </button>\n\n <router-link class=\"w-100 bg-black t-white radius-small button\" to=\"/notifications\" @click=\"closeNotifications\">\n View all \n </router-link>\n </div>\n\n </div>\n\n </div>\n </Popup>\n </div>\n</template>\n\n<script setup>\nimport { ref, computed, onMounted, inject } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport Popup from '@martyrs/src/components/Popup/Popup.vue';\n\nimport NotificationItem from '../blocks/NotificationItem.vue';\nimport * as auth from '@martyrs/src/modules/auth/views/store/auth.js';\nimport IconBell from '@martyrs/src/modules/icons/entities/IconBell.vue';\n\nconst props = defineProps({\n maxNotifications: {\n type: Number,\n default: 10\n },\n fill: {\n type: String,\n default: 'rgb(var(--white))'\n }\n});\n\n// Get router and notification functionality\nconst router = useRouter();\n\n// Check if notifications module is loaded\nconst useNotifications = inject('useNotifications');\n\n// Provide fallback values if module is not loaded\nconst { notifications, unreadCount, loading, markAllAsRead, getNotifications } = useNotifications ? useNotifications() : {\n notifications: ref([]),\n unreadCount: ref(0),\n loading: ref(false),\n markAllAsRead: () => {},\n getNotifications: () => {}\n};\n\n// Local state\nconst isOpen = ref(false);\n\n// Computed properties\nconst recentNotifications = computed(() => {\n return notifications.value\n .slice()\n .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))\n .slice(0, props.maxNotifications);\n});\n\n// Methods\nconst toggleNotifications = () => {\n isOpen.value = !isOpen.value;\n};\n\nconst closeNotifications = () => {\n isOpen.value = false;\n};\n\nconst handleNotificationClick = (notification) => {\n isOpen.value = false;\n \n if (notification._id) {\n // Handle in store\n actions.handleNotificationAction({\n notificationId: notification._id,\n ...notification.metadata\n });\n }\n};\n\n// Lifecycle hooks\nonMounted(() => {\n // Load notifications when component mounts\n const userId = auth.state.user._id;\n if (userId) {\n getNotifications(userId);\n }\n});\n</script>\n\n<style scoped>\n.notification-badge-container {\n position: relative;\n display: inline-block;\n}\n\n.notification-button {\n background: none;\n border: none;\n cursor: pointer;\n position: relative;\n font-size: 1.2rem;\n}\n\n.notification-icon {\n font-size: 1.4rem;\n}\n\n.button-counter {\n position: absolute;\n right: -8px;\n bottom: -8px;\n background: rgb(var(--fourth));\n color: rgb(var(--white));\n height: 16px;\n border-radius: 16px;\n width: 16px;\n font-weight: 500;\n text-align: center;\n line-height: 16px;\n font-size: 10px;\n}\n\n\n.notifications-loading,\n.notifications-empty {\n padding: 24px;\n text-align: center;\n color: rgb(var(--text-light));\n}\n</style>"],"names":["auth.state"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,UAAM,QAAQ;AAYC,cAAS;AAGxB,UAAM,mBAAmB,OAAO,kBAAkB;AAGlD,UAAM,EAAE,eAAe,aAAa,SAAS,eAAe,qBAAqB,mBAAmB,qBAAqB;AAAA,MACvH,eAAe,IAAI,EAAE;AAAA,MACrB,aAAa,IAAI,CAAC;AAAA,MAClB,SAAS,IAAI,KAAK;AAAA,MAClB,eAAe,MAAM;AAAA,MAAC;AAAA,MACtB,kBAAkB,MAAM;AAAA,MAAC;AAAA,IAC3B;AAGA,UAAM,SAAS,IAAI,KAAK;AAGxB,UAAM,sBAAsB,SAAS,MAAM;AACzC,aAAO,cAAc,MAClB,MAAK,EACL,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,IAAI,IAAI,KAAK,EAAE,SAAS,CAAC,EAC5D,MAAM,GAAG,MAAM,gBAAgB;AAAA,IACpC,CAAC;AAGD,UAAM,sBAAsB,MAAM;AAChC,aAAO,QAAQ,CAAC,OAAO;AAAA,IACzB;AAEA,UAAM,qBAAqB,MAAM;AAC/B,aAAO,QAAQ;AAAA,IACjB;AAEA,UAAM,0BAA0B,CAAC,iBAAiB;AAChD,aAAO,QAAQ;AAEf,UAAI,aAAa,KAAK;AAEpB,gBAAQ,yBAAyB;AAAA,UAC/B,gBAAgB,aAAa;AAAA,UAC7B,GAAG,aAAa;AAAA,QACtB,CAAK;AAAA,MACH;AAAA,IACF;AAGA,cAAU,MAAM;AAEd,YAAM,SAASA,MAAW,KAAK;AAC/B,UAAI,QAAQ;AACV,yBAAiB,MAAM;AAAA,MACzB;AAAA,IACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -116,6 +116,26 @@ const NotificationsController = (db, wss, notificationService) => {
116
116
  if (!userId && !anonymousId) {
117
117
  return res.status(400).json({ message: "Either userId or anonymousId is required" });
118
118
  }
119
+ let existingDevice = await db.userDevice.findOne({ deviceToken });
120
+ if (existingDevice) {
121
+ console.log("[RegisterDevice] Found existing device by token, updating...");
122
+ if (userId) {
123
+ existingDevice.userId = userId;
124
+ existingDevice.anonymousId = void 0;
125
+ existingDevice.isAnonymous = false;
126
+ } else {
127
+ existingDevice.anonymousId = anonymousId;
128
+ existingDevice.userId = void 0;
129
+ existingDevice.isAnonymous = true;
130
+ }
131
+ existingDevice.deviceId = deviceId;
132
+ existingDevice.deviceType = deviceType;
133
+ existingDevice.lastActive = Date.now();
134
+ existingDevice.isActive = true;
135
+ await existingDevice.save();
136
+ console.log("[RegisterDevice] Device updated:", existingDevice._id);
137
+ return res.status(200).json(existingDevice);
138
+ }
119
139
  let filter;
120
140
  if (userId) {
121
141
  filter = { userId, deviceId };
@@ -114,6 +114,26 @@ const NotificationsController = (db, wss, notificationService) => {
114
114
  if (!userId && !anonymousId) {
115
115
  return res.status(400).json({ message: "Either userId or anonymousId is required" });
116
116
  }
117
+ let existingDevice = await db.userDevice.findOne({ deviceToken });
118
+ if (existingDevice) {
119
+ console.log("[RegisterDevice] Found existing device by token, updating...");
120
+ if (userId) {
121
+ existingDevice.userId = userId;
122
+ existingDevice.anonymousId = void 0;
123
+ existingDevice.isAnonymous = false;
124
+ } else {
125
+ existingDevice.anonymousId = anonymousId;
126
+ existingDevice.userId = void 0;
127
+ existingDevice.isAnonymous = true;
128
+ }
129
+ existingDevice.deviceId = deviceId;
130
+ existingDevice.deviceType = deviceType;
131
+ existingDevice.lastActive = Date.now();
132
+ existingDevice.isActive = true;
133
+ await existingDevice.save();
134
+ console.log("[RegisterDevice] Device updated:", existingDevice._id);
135
+ return res.status(200).json(existingDevice);
136
+ }
117
137
  let filter;
118
138
  if (userId) {
119
139
  filter = { userId, deviceId };
package/dist/style.css CHANGED
@@ -1398,21 +1398,21 @@ to {
1398
1398
  margin-left: 0.5rem;
1399
1399
  }
1400
1400
 
1401
- .notification-badge-container[data-v-c9d03610] {
1401
+ .notification-badge-container[data-v-f08c11b0] {
1402
1402
  position: relative;
1403
1403
  display: inline-block;
1404
1404
  }
1405
- .notification-button[data-v-c9d03610] {
1405
+ .notification-button[data-v-f08c11b0] {
1406
1406
  background: none;
1407
1407
  border: none;
1408
1408
  cursor: pointer;
1409
1409
  position: relative;
1410
1410
  font-size: 1.2rem;
1411
1411
  }
1412
- .notification-icon[data-v-c9d03610] {
1412
+ .notification-icon[data-v-f08c11b0] {
1413
1413
  font-size: 1.4rem;
1414
1414
  }
1415
- .button-counter[data-v-c9d03610] {
1415
+ .button-counter[data-v-f08c11b0] {
1416
1416
  position: absolute;
1417
1417
  right: -8px;
1418
1418
  bottom: -8px;
@@ -1426,8 +1426,8 @@ to {
1426
1426
  line-height: 16px;
1427
1427
  font-size: 10px;
1428
1428
  }
1429
- .notifications-loading[data-v-c9d03610],
1430
- .notifications-empty[data-v-c9d03610] {
1429
+ .notifications-loading[data-v-f08c11b0],
1430
+ .notifications-empty[data-v-f08c11b0] {
1431
1431
  padding: 24px;
1432
1432
  text-align: center;
1433
1433
  color: rgb(var(--text-light));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ozdao/martyrs",
3
- "version": "0.2.536",
3
+ "version": "0.2.537",
4
4
  "description": "Fullstack framework focused on user experience and ease of development.",
5
5
  "author": "OZ DAO <hello@ozdao.dev>",
6
6
  "license": "GPL-3.0-or-later",
@@ -114,9 +114,9 @@ export class ModuleRegistry {
114
114
  for (const [name, config] of this.loaders) {
115
115
  if (config.routes.some(route => {
116
116
  if (typeof route === 'string') {
117
- // Для корневого роута проверяем точное совпадение
117
+ // Для корневого роута матчим все пути
118
118
  if (route === '/') {
119
- return normalizedPath === '/';
119
+ return true;
120
120
  }
121
121
  // Для остальных роутов проверяем начало пути
122
122
  // но учитываем границы сегментов (не /events должен матчить /event)
@@ -82,7 +82,18 @@ const props = defineProps({
82
82
 
83
83
  // Get router and notification functionality
84
84
  const router = useRouter();
85
- const { notifications, unreadCount, loading, markAllAsRead, getNotifications } = inject('useNotifications')();
85
+
86
+ // Check if notifications module is loaded
87
+ const useNotifications = inject('useNotifications');
88
+
89
+ // Provide fallback values if module is not loaded
90
+ const { notifications, unreadCount, loading, markAllAsRead, getNotifications } = useNotifications ? useNotifications() : {
91
+ notifications: ref([]),
92
+ unreadCount: ref(0),
93
+ loading: ref(false),
94
+ markAllAsRead: () => {},
95
+ getNotifications: () => {}
96
+ };
86
97
 
87
98
  // Local state
88
99
  const isOpen = ref(false);
@@ -129,6 +129,37 @@ const NotificationsController = (db, wss, notificationService) => {
129
129
  return res.status(400).json({ message: 'Either userId or anonymousId is required' });
130
130
  }
131
131
 
132
+ // First, try to find existing device by deviceToken
133
+ let existingDevice = await db.userDevice.findOne({ deviceToken });
134
+
135
+ if (existingDevice) {
136
+ // Device with this token already exists, update it
137
+ console.log('[RegisterDevice] Found existing device by token, updating...');
138
+
139
+ if (userId) {
140
+ // Transfer device from anonymous to authenticated user
141
+ existingDevice.userId = userId;
142
+ existingDevice.anonymousId = undefined;
143
+ existingDevice.isAnonymous = false;
144
+ } else {
145
+ // Update anonymous device
146
+ existingDevice.anonymousId = anonymousId;
147
+ existingDevice.userId = undefined;
148
+ existingDevice.isAnonymous = true;
149
+ }
150
+
151
+ // Update common fields
152
+ existingDevice.deviceId = deviceId;
153
+ existingDevice.deviceType = deviceType;
154
+ existingDevice.lastActive = Date.now();
155
+ existingDevice.isActive = true;
156
+
157
+ await existingDevice.save();
158
+ console.log('[RegisterDevice] Device updated:', existingDevice._id);
159
+ return res.status(200).json(existingDevice);
160
+ }
161
+
162
+ // No existing device with this token, proceed with original logic
132
163
  // Build the filter based on whether user is authenticated or anonymous
133
164
  let filter;
134
165
  if (userId) {