|
|
@@ -1,202 +1,156 @@
|
|
|
# Загрузка стилей компонента
|
|
|
document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss" page="Page">'+stylFns['app/pages/Page/index.styl']+'</style>')
|
|
|
|
|
|
-# Подключение markdown парсера
|
|
|
-marked = require 'marked'
|
|
|
-
|
|
|
module.exports =
|
|
|
- name: 'Page'
|
|
|
+ name: 'PageView'
|
|
|
render: (new Function '_ctx', '_cache', renderFns['app/pages/Page/index.pug'])()
|
|
|
|
|
|
data: ->
|
|
|
page: null
|
|
|
loading: true
|
|
|
error: null
|
|
|
- notFound: false
|
|
|
- pageSettings: {}
|
|
|
-
|
|
|
- computed:
|
|
|
- # Текущий язык из глобального состояния
|
|
|
- currentLanguage: ->
|
|
|
- return _.currentLanguage || 'ru'
|
|
|
-
|
|
|
- # Настройки сайта из глобального состояния
|
|
|
- siteSettings: ->
|
|
|
- return _.appState?.siteSettings || {}
|
|
|
-
|
|
|
- # Обработанный markdown контент
|
|
|
- processedContent: ->
|
|
|
- if not @page?.content
|
|
|
- return ''
|
|
|
-
|
|
|
- # Получаем контент для текущего языка
|
|
|
- content = @getMultilingualText(@page.content, '')
|
|
|
- return @processMarkdown(content)
|
|
|
-
|
|
|
- # Мета-данные для SEO
|
|
|
- pageMeta: ->
|
|
|
- if not @page
|
|
|
- return {}
|
|
|
-
|
|
|
- return {
|
|
|
- title: @getMultilingualText(@page.seo?.title, @getMultilingualText(@page.title, '')),
|
|
|
- description: @getMultilingualText(@page.seo?.description, @getMultilingualText(@page.excerpt, '')),
|
|
|
- keywords: @getMultilingualText(@page.seo?.keywords, []).join(', ')
|
|
|
- }
|
|
|
+ processedContent: ''
|
|
|
+ components: {}
|
|
|
|
|
|
beforeMount: ->
|
|
|
- @loadPageSettings()
|
|
|
@loadPageData()
|
|
|
|
|
|
- watch:
|
|
|
- '$route.params.slug': ->
|
|
|
- @loadPageData()
|
|
|
-
|
|
|
- 'currentLanguage': ->
|
|
|
- @loadPageData()
|
|
|
-
|
|
|
methods:
|
|
|
- # Загрузка настроек страниц
|
|
|
- loadPageSettings: ->
|
|
|
- @pageSettings = _.appState?.siteSettings?.pages || {
|
|
|
- urls: {
|
|
|
- about: '/about'
|
|
|
- contacts: '/contacts'
|
|
|
- privacy: '/privacy'
|
|
|
- terms: '/terms'
|
|
|
- }
|
|
|
- components: {
|
|
|
- about: 'AboutPage'
|
|
|
- contacts: 'ContactsPage'
|
|
|
- }
|
|
|
- strings: {
|
|
|
- not_found: ['Страница не найдена', 'Page not found', 'Саҳифа ёфт нашуд']
|
|
|
- back_to_home: ['Вернуться на главную', 'Back to home', 'Бозгашт ба саҳифаи асосӣ']
|
|
|
- loading: ['Загрузка...', 'Loading...', 'Бор шуда истодааст...']
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
# Загрузка данных страницы
|
|
|
loadPageData: ->
|
|
|
- @loading = true
|
|
|
- @error = null
|
|
|
- @notFound = false
|
|
|
-
|
|
|
- slug = @$route.params.slug
|
|
|
- if not slug
|
|
|
- @error = "Не указан slug страницы"
|
|
|
- @loading = false
|
|
|
- return
|
|
|
-
|
|
|
- @loadPageBySlug(slug).then (page) =>
|
|
|
- if page
|
|
|
- @.page = page
|
|
|
- @updatePageMeta()
|
|
|
+ try
|
|
|
+ @loading = true
|
|
|
+ pageId = @$route.params.id
|
|
|
+
|
|
|
+ if pageId
|
|
|
+ # Загрузка конкретной страницы по ID
|
|
|
+ @page = await AppDB.getDocumentById(pageId)
|
|
|
+ else
|
|
|
+ # Загрузка по slug из пути
|
|
|
+ slug = @$route.path.replace(/^\//, '')
|
|
|
+ @page = await @findPageBySlug(slug)
|
|
|
+
|
|
|
+ if @page
|
|
|
+ @processPageContent()
|
|
|
else
|
|
|
- @.notFound = true
|
|
|
+ @error = "Страница не найдена"
|
|
|
+
|
|
|
@loading = false
|
|
|
- .catch (error) =>
|
|
|
+
|
|
|
+ catch error
|
|
|
@error = "Ошибка загрузки страницы: "+error
|
|
|
@loading = false
|
|
|
|
|
|
# Поиск страницы по slug
|
|
|
- loadPageBySlug: (slug) ->
|
|
|
- return new Promise (resolve, reject) =>
|
|
|
- try
|
|
|
- # Ищем в кэше глобального состояния
|
|
|
- cachedPages = _.appState?.pages || []
|
|
|
- cachedPage = cachedPages.find (page) =>
|
|
|
- slugs = @getMultilingualText(page.slug, [])
|
|
|
- return slugs.includes(slug)
|
|
|
-
|
|
|
- if cachedPage
|
|
|
- resolve(cachedPage)
|
|
|
- return
|
|
|
-
|
|
|
- # Если нет в кэше, загружаем из базы
|
|
|
- AppDB.db.query('pages/by_slug_multilingual', {
|
|
|
- key: ['borbad.s5l.ru', @currentLanguage, slug]
|
|
|
- include_docs: true
|
|
|
- }).then (result) =>
|
|
|
- if result.rows.length > 0
|
|
|
- page = AppDB.processMultilingualDocument(result.rows[0].doc)
|
|
|
- # Сохраняем в кэш
|
|
|
- if not _.appState.pages
|
|
|
- _.appState.pages = []
|
|
|
- _.appState.pages.push(page)
|
|
|
- resolve(page)
|
|
|
- else
|
|
|
- resolve(null)
|
|
|
- .catch (error) ->
|
|
|
- reject(error)
|
|
|
-
|
|
|
- catch error
|
|
|
- reject(error)
|
|
|
+ findPageBySlug: (slug) ->
|
|
|
+ try
|
|
|
+ result = await AppDB.db.query('multilingual_content/published_by_domain_language', {
|
|
|
+ startkey: [_.currentDomain, _.currentLanguage, 'page']
|
|
|
+ endkey: [_.currentDomain, _.currentLanguage, 'page', {}]
|
|
|
+ include_docs: true
|
|
|
+ })
|
|
|
+
|
|
|
+ for row in result.rows
|
|
|
+ doc = row.doc
|
|
|
+ pageSlug = AppDB.multilingual.getText(doc.slug, '')
|
|
|
+ if pageSlug == slug
|
|
|
+ return AppDB.processMultilingualDocument(doc)
|
|
|
+
|
|
|
+ return null
|
|
|
+ catch error
|
|
|
+ debug.log "Ошибка поиска страницы по slug: "+error
|
|
|
+ return null
|
|
|
|
|
|
- # Обработка markdown с поддержкой кастомных классов
|
|
|
- processMarkdown: (content) ->
|
|
|
- if not content
|
|
|
- return ''
|
|
|
-
|
|
|
- # Обрабатываем кастомные теги для классов
|
|
|
- processedContent = content.replace(/\[class:([^\]]+)\]/g, '<div class="$1">')
|
|
|
- .replace(/\[\/class\]/g, '</div>')
|
|
|
+ # Обработка контента с компонентами
|
|
|
+ processPageContent: ->
|
|
|
+ if not @page?.content
|
|
|
+ @processedContent = ''
|
|
|
+ return
|
|
|
|
|
|
- # Настройка marked
|
|
|
- marked.setOptions({
|
|
|
- breaks: true
|
|
|
- gfm: true
|
|
|
- sanitize: false # Разрешаем HTML для кастомных классов
|
|
|
- })
|
|
|
+ # Получаем контент для текущего языка
|
|
|
+ content = AppDB.multilingual.getText(@page.content, '')
|
|
|
|
|
|
- return marked(processedContent)
|
|
|
-
|
|
|
- # Получение мультиязычного текста
|
|
|
- getMultilingualText: (textArray, fallback = '') ->
|
|
|
- return AppDB.multilingual.getText(textArray, fallback)
|
|
|
+ # Обрабатываем компоненты в Markdown
|
|
|
+ @processedContent = @parseComponents(content)
|
|
|
|
|
|
- # Получение локализованной строки из настроек
|
|
|
- getLocalizedString: (key, fallback = '') ->
|
|
|
- strings = @pageSettings.strings?[key] || []
|
|
|
- return @getMultilingualText(strings, fallback)
|
|
|
-
|
|
|
- # Обновление мета-данных страницы
|
|
|
- updatePageMeta: ->
|
|
|
- if @pageMeta.title
|
|
|
- document.title = @pageMeta.title + ' - Кохи Борбад'
|
|
|
+ # Парсинг компонентов в Markdown
|
|
|
+ parseComponents: (content) ->
|
|
|
+ # Регулярное выражение для поиска компонентов
|
|
|
+ componentPattern = /\[component\s+name="([^"]+)"\s*(?:props='([^']*)')?\s*\]/g
|
|
|
|
|
|
- # Обновляем meta теги
|
|
|
- metaDescription = document.querySelector('meta[name="description"]')
|
|
|
- if not metaDescription
|
|
|
- metaDescription = document.createElement('meta')
|
|
|
- metaDescription.name = 'description'
|
|
|
- document.head.appendChild(metaDescription)
|
|
|
- metaDescription.content = @pageMeta.description
|
|
|
+ # Заменяем компоненты на HTML-разметку
|
|
|
+ processedContent = content.replace componentPattern, (match, componentName, propsJson = '{}') =>
|
|
|
+ try
|
|
|
+ props = JSON.parse(propsJson)
|
|
|
+ catch
|
|
|
+ props = {}
|
|
|
+
|
|
|
+ # Создаем уникальный ID для компонента
|
|
|
+ componentId = "component_"+Math.random().toString(36).substr(2, 9)
|
|
|
+
|
|
|
+ # Регистрируем компонент для рендеринга
|
|
|
+ @components[componentId] = {
|
|
|
+ name: componentName
|
|
|
+ props: props
|
|
|
+ }
|
|
|
+
|
|
|
+ # Возвращаем placeholder для Vue компонента
|
|
|
+ return '<div class="dynamic-component" data-component-id="'+componentId+'"></div>'
|
|
|
|
|
|
- metaKeywords = document.querySelector('meta[name="keywords"]')
|
|
|
- if not metaKeywords
|
|
|
- metaKeywords = document.createElement('meta')
|
|
|
- metaKeywords.name = 'keywords'
|
|
|
- document.head.appendChild(metaKeywords)
|
|
|
- metaKeywords.content = @pageMeta.keywords
|
|
|
+ return processedContent
|
|
|
|
|
|
- # Рендер компонента указанного в настройках страницы
|
|
|
- renderCustomComponent: ->
|
|
|
- if not @page?.settings?.component
|
|
|
- return null
|
|
|
-
|
|
|
- componentName = @page.settings.component
|
|
|
+ # Рендеринг динамических компонентов
|
|
|
+ renderDynamicComponents: ->
|
|
|
+ elements = document.querySelectorAll('.dynamic-component')
|
|
|
+ for element in elements
|
|
|
+ componentId = element.getAttribute('data-component-id')
|
|
|
+ componentInfo = @components[componentId]
|
|
|
+
|
|
|
+ if componentInfo
|
|
|
+ try
|
|
|
+ # Загружаем компонент динамически
|
|
|
+ componentModule = await @loadComponent(componentInfo.name)
|
|
|
+ if componentModule
|
|
|
+ # Создаем и монтируем компонент
|
|
|
+ componentInstance = Vue.createAppComponent(
|
|
|
+ componentModule.default or componentModule
|
|
|
+ componentInfo.props
|
|
|
+ )
|
|
|
+ componentInstance.mount(element)
|
|
|
+ catch error
|
|
|
+ debug.log "Ошибка загрузки компонента "+componentInfo.name+": "+error
|
|
|
+
|
|
|
+ # Динамическая загрузка компонента
|
|
|
+ loadComponent: (componentName) ->
|
|
|
+ # Преобразуем имя компонента в путь
|
|
|
+ componentPath = @getComponentPath(componentName)
|
|
|
try
|
|
|
- component = require('app/pages/' + componentName)
|
|
|
- return component
|
|
|
+ return await import(componentPath)
|
|
|
catch error
|
|
|
- debug.log "Компонент "+componentName+" не найден: "+error
|
|
|
+ debug.log "Компонент не найден: "+componentPath
|
|
|
return null
|
|
|
|
|
|
- # Обработка клика по внутренним ссылкам в контенте
|
|
|
- handleContentClick: (event) ->
|
|
|
- if event.target.tagName == 'A'
|
|
|
- href = event.target.getAttribute('href')
|
|
|
- if href and href.startsWith('/')
|
|
|
- event.preventDefault()
|
|
|
- _.$router.push(href)
|
|
|
+ # Получение пути к компоненту
|
|
|
+ getComponentPath: (componentName) ->
|
|
|
+ # Базовая карта компонентов
|
|
|
+ componentMap =
|
|
|
+ 'HeroSection': '@/app/shared/HeroSection'
|
|
|
+ 'ImageGallery': '@/app/shared/ImageGallery'
|
|
|
+ 'ContactForm': '@/app/shared/ContactForm'
|
|
|
+ 'EventList': '@/app/shared/EventList'
|
|
|
+ 'Testimonials': '@/app/shared/Testimonials'
|
|
|
+
|
|
|
+ return componentMap[componentName] or '@/app/shared/'+componentName
|
|
|
+
|
|
|
+ # Получение текста с учетом языка
|
|
|
+ getText: (textArray) ->
|
|
|
+ return AppDB.multilingual.getText(textArray, '')
|
|
|
+
|
|
|
+ mounted: ->
|
|
|
+ # Рендерим динамические компоненты после монтирования
|
|
|
+ @$nextTick =>
|
|
|
+ @renderDynamicComponents()
|
|
|
+
|
|
|
+ watch:
|
|
|
+ '$route': 'loadPageData'
|
|
|
+ '_.currentLanguage': 'loadPageData'
|