Gogs 3 săptămâni în urmă
părinte
comite
82c59eea6c

+ 432 - 0
vue/app/shared/AppLink/README.md

@@ -0,0 +1,432 @@
+# Компонент AppLink - Полная документация
+
+## Назначение компонента
+
+`AppLink` - универсальный компонент для работы со ссылками в приложении. Автоматически определяет тип ссылки (внутренняя/внешняя) и использует соответствующий компонент (`router-link` для внутренних маршрутов, `a` для внешних URL). Обеспечивает единообразие поведения, безопасности и стилизации всех ссылок в приложении.
+
+## Импорт и регистрация
+
+```coffee
+# В основном файле приложения (app/temp.coffee)
+components:
+    'app-link': require 'app/shared/AppLink'
+```
+
+## Базовое использование
+
+### 1. Простая внутренняя ссылка
+```pug
+app-link(to="/about") О нас
+```
+
+### 2. Внешняя ссылка
+```pug
+app-link(to="https://example.com") Внешний сайт
+```
+
+## Полный список props
+
+### `to` (обязательный)
+- **Тип:** `String | Object`
+- **Описание:** Назначение ссылки
+- **Примеры:**
+  ```pug
+  //- Строковый путь
+  app-link(to="/events")
+  
+  //- Объект маршрута Vue Router
+  app-link(:to="{ name: 'EventDetail', params: { id: event._id } }")
+  
+  //- Внешний URL
+  app-link(to="https://facebook.com/borbad")
+  
+  //- Email ссылка
+  app-link(to="mailto:info@borbad.s5l.ru")
+  
+  //- Телефонная ссылка
+  app-link(to="tel:+992371234567")
+  ```
+
+### `href` (опциональный)
+- **Тип:** `String`
+- **По умолчанию:** `''`
+- **Описание:** Альтернативный способ указания внешнего URL
+- **Пример:**
+  ```pug
+  app-link(href="https://example.com" to="/fallback") Ссылка
+  ```
+
+### `target` (опциональный)
+- **Тип:** `String`
+- **По умолчанию:** `'_self'`
+- **Допустимые значения:** `'_self'`, `'_blank'`, `'_parent'`, `'_top'`
+- **Описание:** Целевое окно для ссылки
+- **Пример:**
+  ```pug
+  app-link(to="https://example.com" target="_blank") Открыть в новом окне
+  ```
+
+### `rel` (опциональный)
+- **Тип:** `String`
+- **По умолчанию:** `''`
+- **Описание:** Атрибут rel для внешних ссылок
+- **Примечание:** Для `target="_blank"` автоматически добавляется `noopener noreferrer`
+- **Пример:**
+  ```pug
+  app-link(to="https://example.com" rel="nofollow") Ссылка nofollow
+  ```
+
+### `class` (опциональный)
+- **Тип:** `String | Object | Array`
+- **По умолчанию:** `''`
+- **Описание:** CSS классы для стилизации
+- **Примеры:**
+  ```pug
+  //- Строка
+  app-link(to="/" class="text-blue-600 hover:text-blue-800")
+  
+  //- Объект
+  app-link(to="/" :class="{ 'font-bold': isActive }")
+  
+  //- Массив
+  app-link(to="/" :class="['text-blue-600', 'hover:text-blue-800']")
+  ```
+
+### `activeClass` (опциональный)
+- **Тип:** `String`
+- **По умолчанию:** `'app-link--active'`
+- **Описание:** CSS класс для активного состояния (только для внутренних ссылок)
+- **Пример:**
+  ```pug
+  app-link(to="/events" active-class="bg-blue-100 text-blue-800")
+  ```
+
+### `exact` (опциональный)
+- **Тип:** `Boolean`
+- **По умолчанию:** `false`
+- **Описание:** Точное совпадение для активного класса
+- **Пример:**
+  ```pug
+  app-link(to="/" exact) Главная
+  ```
+
+## События (Events)
+
+### `@click`
+- **Описание:** Событие клика по ссылке
+- **Payload:** `Event` объект
+- **Пример:**
+  ```pug
+  app-link(to="/events" @click="handleEventClick")
+  ```
+
+## Слоты (Slots)
+
+### `[body]` (основной слот)
+- **Описание:** Содержимое ссылки
+- **Примеры:**
+  ```pug
+  //- Текстовое содержимое
+  app-link(to="/events") Все мероприятия
+  
+  //- HTML содержимое
+  app-link(to="/events")
+      div(class="flex items-center")
+          span Мероприятия
+          icon(name="arrow-right")
+  
+  //- Компоненты внутри
+  app-link(to="/cart")
+      cart-icon
+      span Корзина
+      badge(:count="cartCount")
+  ```
+
+## Стили и CSS классы
+
+### Базовые классы
+- `.app-link` - основной класс компонента
+- `.app-link--active` - класс активного состояния (по умолчанию)
+
+### Модификаторы размера
+```pug
+app-link(to="/" class="app-link--sm") Маленькая
+app-link(to="/" class="app-link--md") Средняя
+app-link(to="/" class="app-link--lg") Большая
+```
+
+### Варианты стилей
+```pug
+//- Основной стиль
+app-link(to="/" class="app-link--primary") Основная
+
+//- Вторичный стиль
+app-link(to="/" class="app-link--secondary") Вторичная
+
+//- Текстовый стиль
+app-link(to="/" class="app-link--text") Текстовая
+
+//- С иконкой внешней ссылки
+app-link(to="https://example.com" class="app-link--external") Внешняя
+```
+
+### Утилитарные классы
+```pug
+//- Без подчеркивания
+app-link(to="/" class="no-underline") Без подчеркивания
+
+//- Подчеркивание при hover
+app-link(to="/" class="underline-on-hover") Подчеркивание при наведении
+```
+
+## Примеры использования в различных сценариях
+
+### 1. Навигационное меню
+```pug
+nav(class="flex space-x-6")
+    app-link(
+        to="/"
+        exact
+        active-class="text-blue-600 border-blue-600"
+        class="pb-2 border-b-2 border-transparent transition-colors"
+    ) Главная
+    
+    app-link(
+        to="/events"
+        active-class="text-blue-600 border-blue-600"
+        class="pb-2 border-b-2 border-transparent transition-colors"
+    ) Мероприятия
+    
+    app-link(
+        to="/blog"
+        active-class="text-blue-600 border-blue-600"
+        class="pb-2 border-b-2 border-transparent transition-colors"
+    ) Блог
+```
+
+### 2. Карточки мероприятий
+```pug
+div(
+    v-for="event in events"
+    :key="event._id"
+    class="bg-white rounded-lg shadow-md overflow-hidden"
+)
+    app-link(:to="'/events/' + event._id")
+        img(:src="event.image" :alt="event.title" class="w-full h-48 object-cover")
+        div(class="p-4")
+            h3(class="text-lg font-semibold mb-2") {{ event.title }}
+            p(class="text-gray-600 mb-2") {{ event.event_data.location }}
+            p(class="text-gray-500 text-sm") {{ formatDate(event.event_data.event_date) }}
+```
+
+### 3. Социальные ссылки
+```pug
+div(class="flex space-x-4")
+    app-link(
+        to="https://facebook.com/borbad"
+        target="_blank"
+        rel="noopener noreferrer"
+        class="app-link--external text-gray-600 hover:text-blue-600"
+    )
+        icon(name="facebook" class="w-6 h-6")
+    
+    app-link(
+        to="https://instagram.com/borbad"
+        target="_blank"
+        rel="noopener noreferrer"
+        class="app-link--external text-gray-600 hover:text-pink-600"
+    )
+        icon(name="instagram" class="w-6 h-6")
+```
+
+### 4. Кнопки действия
+```pug
+div(class="flex space-x-4")
+    app-link(
+        to="/events"
+        class="app-link--primary app-link--lg"
+    ) Смотреть мероприятия
+    
+    app-link(
+        to="/contacts"
+        class="app-link--secondary app-link--lg"
+    ) Связаться с нами
+```
+
+### 5. Ссылки с параметрами запроса
+```pug
+app-link(
+    :to="{ 
+        path: '/events', 
+        query: { 
+            category: 'music',
+            date: '2024-03'
+        }
+    }"
+    class="text-blue-600 hover:text-blue-800"
+) Музыкальные мероприятия марта
+```
+
+### 6. Ссылки с именованными маршрутами
+```pug
+app-link(
+    :to="{ 
+        name: 'BlogPost', 
+        params: { 
+            id: post._id,
+            slug: post.slug
+        }
+    }"
+    class="block p-6 bg-white rounded-lg shadow hover:shadow-md transition-shadow"
+)
+    h3(class="text-xl font-semibold mb-2") {{ post.title }}
+    p(class="text-gray-600") {{ post.excerpt }}
+```
+
+### 7. Email и телефонные ссылки
+```pug
+div(class="space-y-2")
+    app-link(
+        to="mailto:info@borbad.s5l.ru"
+        class="text-blue-600 hover:text-blue-800"
+    ) info@borbad.s5l.ru
+    
+    app-link(
+        to="tel:+992371234567"
+        class="text-blue-600 hover:text-blue-800"
+    ) +992 37 123-45-67
+```
+
+## Особенности работы
+
+### Автоматическое определение типа ссылки
+```coffee
+# Внутренние ссылки
+'/about'
+'/events/123'
+{ name: 'Home' }
+{ path: '/blog', query: { category: 'news' } }
+
+# Внешние ссылки
+'https://example.com'
+'//cdn.example.com/image.jpg'
+'mailto:test@example.com'
+'tel:+1234567890'
+'ftp://example.com'
+'#section'
+```
+
+### Безопасность внешних ссылок
+- Автоматическое добавление `rel="noopener noreferrer"` для `target="_blank"`
+- Защита от уязвимостей типа tabnabbing
+- Валидация URL
+
+### Интеграция с аналитикой
+```coffee
+# В обработчиках событий
+handleExternalClick: (event) ->
+    EventBus.emit('external_link_clicked', {
+        url: @normalizedHref
+        target: @target
+    })
+
+handleInternalClick: (event) ->
+    EventBus.emit('internal_link_clicked', {
+        to: @normalizedTo
+        route: @$route
+    })
+```
+
+## Кастомизация
+
+### Создание собственных вариантов стилей
+```styl
+// В глобальных стилях
+.app-link--custom
+    @apply bg-gradient-to-r from-purple-500 to-pink-500 text-white
+    
+    &:hover
+        @apply from-purple-600 to-pink-600
+    
+    &.app-link--active
+        @apply from-purple-700 to-pink-700
+```
+
+### Расширение функциональности
+```coffee
+# Создание производного компонента
+CustomAppLink = 
+    name: 'CustomAppLink'
+    extends: require 'app/shared/AppLink'
+    
+    methods:
+        handleExternalClick: (event) ->
+            # Дополнительная логика
+            analytics.track('external_link_click', { url: @normalizedHref })
+            
+            # Вызов родительского метода
+            @$super.handleExternalClick(event)
+```
+
+## Отладка и разработка
+
+### Проверка типа ссылки
+```pug
+app-link(
+    :to="linkUrl"
+    :class="{ 'border-green-500': $el.isRouterLink, 'border-red-500': $el.isExternalLink }"
+) Ссылка
+```
+
+### Логирование событий
+```coffee
+# В основном приложении
+EventBus.on 'external_link_clicked', (data) ->
+    debug.log "Внешняя ссылка: "+data.url
+
+EventBus.on 'internal_link_clicked', (data) ->
+    debug.log "Внутренняя ссылка: "+JSON.stringify(data.to)
+```
+
+## Best Practices
+
+### 1. Всегда используйте AppLink вместо прямых тегов
+```pug
+//- Правильно
+app-link(to="/about") О нас
+
+//- Неправильно
+a(href="/about") О нас
+router-link(to="/about") О нас
+```
+
+### 2. Используйте правильные target и rel для внешних ссылок
+```pug
+//- Правильно
+app-link(to="https://example.com" target="_blank") Ссылка
+
+//- Опасно (без rel)
+app-link(to="https://example.com" target="_blank" rel="") Ссылка
+```
+
+### 3. Используйте объекты маршрутов для сложных ссылок
+```pug
+//- Правильно
+app-link(:to="{ name: 'EventDetail', params: { id: event._id } }") {{ event.title }}
+
+//- Менее предпочтительно
+app-link(:to="'/events/' + event._id") {{ event.title }}
+```
+
+### 4. Сохраняйте семантику контента в слоте
+```pug
+//- Правильно
+app-link(to="/events")
+    span Мероприятия
+    icon(name="arrow-right")
+
+//- Неправильно (не семантично)
+app-link(to="/events") > Мероприятия >
+```
+
+Компонент AppLink обеспечивает единообразное, безопасное и удобное использование ссылок во всем приложении, автоматически обрабатывая различные сценарии и предоставляя богатые возможности для кастомизации.

+ 30 - 0
vue/app/shared/LanguageSwitcher/index.coffee

@@ -0,0 +1,30 @@
+# Загрузка стилей компонента
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss"  page="LanguageSwitcher">'+stylFns['app/shared/LanguageSwitcher/index.styl']+'</style>')
+
+module.exports =
+    name: 'LanguageSwitcher'
+    render: (new Function '_ctx', '_cache', renderFns['app/shared/LanguageSwitcher/index.pug'])()
+    
+    data: ->
+        isOpen: false
+        languages: [
+            { code: 'ru', name: 'Русский', native: 'Русский' }
+            { code: 'en', name: 'English', native: 'English' }
+            { code: 'tj', name: 'Tajik', native: 'Тоҷикӣ' }
+        ]
+    
+    computed:
+        currentLanguage: ->
+            return _.currentLanguage || 'ru'
+        
+        currentLanguageName: ->
+            lang = @languages.find (l) -> l.code == @currentLanguage
+            return lang?.native || 'Русский'
+    
+    methods:
+        changeLanguage: (languageCode) ->
+            _.changeLanguage(languageCode)
+            @isOpen = false
+        
+        toggleDropdown: ->
+            @isOpen = !@isOpen

+ 32 - 0
vue/app/shared/LanguageSwitcher/index.pug

@@ -0,0 +1,32 @@
+div(class="language-switcher relative")
+    button(
+        @click="toggleDropdown"
+        class="flex items-center space-x-2 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
+    )
+        span(class="text-sm font-medium") {{ currentLanguageName }}
+        svg(
+            :class="{ 'transform rotate-180': isOpen }"
+            class="w-4 h-4 transition-transform"
+            fill="none" 
+            stroke="currentColor" 
+            viewBox="0 0 24 24"
+        )
+            path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7")
+    
+    div(
+        v-if="isOpen"
+        class="absolute top-full right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 z-50"
+    )
+        div(class="py-1")
+            button(
+                v-for="language in languages"
+                :key="language.code"
+                @click="changeLanguage(language.code)"
+                :class="{'bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400': currentLanguage === language.code, 'text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700': currentLanguage !== language.code }"
+                class="w-full text-left px-4 py-2 text-sm transition-colors flex items-center space-x-2"
+            )
+                span {{ language.native }}
+                span(
+                    v-if="currentLanguage === language.code"
+                    class="text-blue-600 dark:text-blue-400"
+                ) ✓

+ 1 - 0
vue/app/shared/LanguageSwitcher/index.styl

@@ -0,0 +1 @@
+