diff --git a/src/client/app.js b/src/client/app.js
index 17587dcd..a3017cc8 100644
--- a/src/client/app.js
+++ b/src/client/app.js
@@ -233,16 +233,25 @@ app.config(($locationProvider, $routeProvider) => {
})
$routeProvider.otherwise({
- templateUrl: '/404.html'
+ template: '',
+ controller: function($window) {
+ console.log('otherwise')
+ $window.location.reload()
+ }
})
})
-app.run(($location, $rootScope, $http) => {
+app.run(($window, $location, $rootScope, $http) => {
$rootScope.$on('$routeChangeSuccess', (event, current) => {
const url = location.href
- const title = current['$$route'].title
- if (title) $rootScope.title = title
+ try {
+ const title = current['$$route'].title
+
+ if (title) $rootScope.title = title
+ } catch (e) {
+
+ }
setTimeout(() => {
try {
diff --git a/src/components/App.js b/src/components/App.js
new file mode 100644
index 00000000..51b72d21
--- /dev/null
+++ b/src/components/App.js
@@ -0,0 +1,31 @@
+const React = require('react')
+const Menu = require('./Menu')
+const Footer = require('./Footer')
+
+const App = ({children, menu}) => (
+
+)
+
+module.exports = App
diff --git a/src/components/Footer.js b/src/components/Footer.js
new file mode 100644
index 00000000..68856c99
--- /dev/null
+++ b/src/components/Footer.js
@@ -0,0 +1,31 @@
+const React = require('react')
+
+const mailto = email => `mailto:${email}`
+
+const Footer = ({children, address, telephone, email, showAuthor}) => (
+
+)
+
+module.exports = Footer
diff --git a/src/components/Menu.js b/src/components/Menu.js
new file mode 100644
index 00000000..5438513f
--- /dev/null
+++ b/src/components/Menu.js
@@ -0,0 +1,39 @@
+const React = require('react')
+
+const hasSubmenu = item => !!item.items
+const doDrawSubmenu = item => false //item.active && hasSubmenu(item)
+
+const selectedClassName = active => active
+ ? 'selected'
+ : ''
+
+const MenuItem = ({active, text, url}) => (active
+ ? {text}
+ : {text}
+)
+
+const Menu = ({items}) => {
+ const content = items.map((item, index) => (
+
+
+ {!doDrawSubmenu(item) ? null :
+
+ }
+
+ ))
+ return (
+
+ )
+}
+
+const Submenu = ({items}) => (
+
+)
+
+module.exports = Menu
diff --git a/src/lib/common.js b/src/lib/common.js
new file mode 100644
index 00000000..0fd2b0db
--- /dev/null
+++ b/src/lib/common.js
@@ -0,0 +1,36 @@
+function compose(...fns) {
+ return value => [...fns]
+ .reverse()
+ .reduce((acc, fn) => fn(acc), value)
+}
+
+/**
+ * AND operator for list of values
+ *
+ * [true, true, true, ...] -> true
+ * [false, true, true, ...] -> false
+ *
+ * @param list
+ * @return {boolean}
+ */
+function all(list) {
+ return list.reduce((t, i) => t && i, true)
+}
+
+/**
+ * OR operator for list of values
+ *
+ * [true, true, true, ...] -> true
+ * [false, true, true, ...] -> true
+ * [false, false, false, ...] -> false
+ *
+ * @param list
+ * @return {boolean}
+ */
+function any(list) {
+ return list.reduce((t, i) => t || i, false)
+}
+
+exports.compose = compose
+exports.all = all
+exports.any = any
diff --git a/src/lib/component.js b/src/lib/component.js
new file mode 100644
index 00000000..8a2dcd47
--- /dev/null
+++ b/src/lib/component.js
@@ -0,0 +1,15 @@
+import React from 'react'
+import {renderToStaticMarkup} from 'react-dom/server'
+
+const App = require('../components/App')
+
+function renderApp({menu, page}) {
+ const app = (
+
+
+
+ )
+ return renderToStaticMarkup(app)
+}
+
+exports.renderApp = renderApp
diff --git a/src/lib/url.js b/src/lib/url.js
new file mode 100644
index 00000000..1b2c3d4a
--- /dev/null
+++ b/src/lib/url.js
@@ -0,0 +1,14 @@
+function getPathWithNoTrailingSlash(path) {
+ return path.charAt(path.length - 1) === '/'
+ ? path.substr(0, path.length - 1)
+ : path
+}
+
+function isEqualPath(a, b, ignoreTrailingSlash = false) {
+ return ignoreTrailingSlash
+ ? getPathWithNoTrailingSlash(a) === getPathWithNoTrailingSlash(b)
+ : a === b
+}
+
+exports.getPathWithNoTrailingSlash = getPathWithNoTrailingSlash
+exports.isEqualPath = isEqualPath
diff --git a/src/middlewares/error.js b/src/middlewares/error.js
new file mode 100644
index 00000000..9e36622a
--- /dev/null
+++ b/src/middlewares/error.js
@@ -0,0 +1,18 @@
+const {readFile} = require('async-file')
+const handlebars = require('handlebars')
+
+function notFound(templateFile) {
+ return async (ctx, next) => {
+ await next()
+
+ if (ctx.status === 404) {
+ const source = await readFile(templateFile, 'utf-8')
+ const template = handlebars.compile(source)
+
+ ctx.type = 'text/html'
+ ctx.body = template()
+ }
+ }
+}
+
+exports.notFound = notFound
diff --git a/src/models/menu.js b/src/models/menu.js
index 1c723afa..ed542083 100644
--- a/src/models/menu.js
+++ b/src/models/menu.js
@@ -1,8 +1,11 @@
export default [
{
- 'text': 'Школа',
- 'url': '/',
- 'color': 'blue'
+ url: '/',
+ text: 'Школа',
+ color: 'blue',
+ items: [
+ {url: '/summer-school/2017', text: 'Летние каникулы 2017'},
+ ],
},
{
'text': 'События',
@@ -20,9 +23,24 @@ export default [
'color': 'yellow'
},
{
- 'text': 'Предметы',
- 'url': '/courses',
- 'color': 'magenta'
+ url: '/courses/',
+ text: 'Предметы',
+ color: 'magenta',
+ items: [
+ {url: 'ceramics', text: 'Керамика'},
+ {url: 'batik', text: 'Живопись'},
+ {url: 'easel-composition', text: 'Живопись'},
+ {url: 'cg', text: 'Живопись'},
+ {url: 'scale-modeling', text: 'Живопись'},
+ {url: 'drawing', text: 'Живопись'},
+ {url: 'costume-composition', text: 'Живопись'},
+ {url: 'felting', text: 'Живопись'},
+ {url: 'art-history', text: 'Живопись'},
+
+ {url: 'perishki', text: 'Живопись'},
+ {url: 'matreshki', text: 'Живопись'},
+ {url: 'listochki', text: 'Живопись'},
+ ]
},
{
'text': 'Коллектив',
@@ -40,9 +58,24 @@ export default [
'color': 'yellow'
},
{
- 'text': 'Сведения об образовательной организации',
- 'url': '/info',
- 'color': 'gray'
+ url: '/info/',
+ text: 'Сведения об образовательной организации',
+ color: 'gray',
+ items: [
+ {url: 'main', text: 'Основные сведения'},
+ {url: 'structure', text: 'Структура и органы управления образовательной организацией'},
+ {url: 'education', text: 'Образование'},
+ {url: 'standards', text: 'Образовательные стандарты'},
+ {url: '/collective', text: 'Руководство. Педагогический (научно-педагогический) состав'},
+ {url: 'support', text: 'Материально-техническое обеспечение образовательного процесса'},
+ {url: 'grants', text: 'Стипендии и иные виды материальной поддержки'},
+ {url: 'paid-services', text: 'Платные образовательные услуги'},
+ {url: 'finances', text: 'Финансово-хозяйственная деятельность'},
+ {url: 'vacancy', text: 'Вакантные места для приема'},
+
+ {url: 'tools', text: 'Список рекомендованных материалов'},
+ {url: 'history', text: 'История школы'},
+ ]
},
{
'text': 'Текущий контроль',
diff --git a/src/routes/404.js b/src/routes/404.js
deleted file mode 100644
index bddf1507..00000000
--- a/src/routes/404.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import {index, accepts} from './';
-
-export default function () {
- return async(ctx) => {
- if (ctx.status != 404) return;
-
- ctx.status = 404;
- ctx.body = 'lol found';
-
- await accepts({
- 'text/html': index(),
- 'text/plain': async ctx => {
- ctx.type = 'text/plain';
- ctx.body = 'Page not found';
- },
-
- 'application/json': async ctx => {
- ctx.type = 'application/json';
- ctx.body = {
- error: 404,
- message: 'Not Found'
- };
- }
- })(ctx);
- };
-};
\ No newline at end of file
diff --git a/src/routes/index.js b/src/routes/index.js
index 4ddaea8e..0e061dd5 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -11,7 +11,6 @@ import schedule from './schedule'
import {getPageUrls} from '../core/pages'
import news from './news'
import gallery from './gallery'
-import error404 from './404'
import documents from './documents'
import teachers from './teachers'
import events from './events'
@@ -32,7 +31,6 @@ export function routes(store) {
events(),
articles(),
pages(),
- error404()
])
}
@@ -96,6 +94,5 @@ export function json(fn) {
}
async function getInitialData() {
- const pages = await getPageUrls()
- return {pages: pages}
+ return {pages: []}
}
diff --git a/src/routes/pages.js b/src/routes/pages.js
index eb2f671c..253adbf7 100644
--- a/src/routes/pages.js
+++ b/src/routes/pages.js
@@ -1,24 +1,71 @@
-import route from 'koa-route'
-import {c} from '../core/db'
-import {accepts, index} from './'
+const {readFile} = require('async-file')
+const handlebars = require('handlebars')
-async function exists(ctx) {
- const page = await getPage(ctx.path)
- return Boolean(page)
-}
+const {get} = require('koa-route')
+const {c} = require('../core/db')
+
+const url = require('url')
+const renderApp = require('../lib/component').renderApp
+const isEqualPath = require('../lib/url').isEqualPath
+const getPathWithNoTrailingSlash = require('../lib/url').getPathWithNoTrailingSlash
+const {compose, any} = require('../lib/common')
+
+const menuModel = require('../models/menu').default
async function getPage(path) {
return await c('pages').findOne({url: path})
}
-export default function () {
- return route.get('*', accepts({
- 'text/html': index(exists),
- 'text/plain': index(exists),
- 'application/json': async(ctx) => {
- const page = await getPage(ctx.path)
- if (!page) ctx.status = 404
- else ctx.body = page
- }
+function isActive(path, menuItem) {
+ return any(flatMenuItem(menuItem)
+ .map(i => isEqualPath(path, i.url, true))
+ )
+}
+
+function flatMenuItem(item) {
+ const rootUrl = item.url
+ const flatUrls = items => {
+ return items.map(item => ({
+ ...item,
+ url: url.resolve(rootUrl, item.url)
+ }))
+ }
+
+ return !item.items
+ ? [item]
+ : item.items.reduce((acc, i) => [...acc, ...compose(flatUrls, flatMenuItem)(i)], [item])
+}
+
+/**
+ * Takes plain menu model and sample path and defines is an item of menu should be active
+ *
+ * @param path
+ * @param menu
+ * @return {{items}}
+ */
+function buildMenu(path, menu) {
+ const items = menu.map(item => ({
+ ...item,
+ active: isActive(path, item)
}))
-};
+ return {items}
+}
+
+module.exports = function () {
+ return get('*', async ctx => {
+ const path = getPathWithNoTrailingSlash(ctx.path)
+ const page = await getPage(path)
+ if (page) {
+ const menu = buildMenu(path, menuModel)
+ const content = renderApp({menu, page})
+
+ const source = await readFile('src/views/main.hbs', 'utf-8')
+ const template = handlebars.compile(source)
+
+ ctx.type = 'text/html'
+ ctx.body = template({content})
+ } else {
+ ctx.status = 404
+ }
+ })
+}
diff --git a/src/server.js b/src/server.js
index 6b2b6ba6..0e3f34f6 100644
--- a/src/server.js
+++ b/src/server.js
@@ -19,6 +19,8 @@ import {redirect} from './routes/redirect'
import {services as serviceKeys, authChecker} from './core/service'
import handlebars from 'handlebars'
+const error = require('./middlewares/error')
+
const dirPublic = path.join(__dirname, '../public')
const dirTemplates = path.join(__dirname, 'templates')
@@ -47,6 +49,8 @@ export default function (store) {
app.use($(redirect(redirectionTable)))
app.use($(helmet()))
app.use(queryObject())
+
+ app.use(error.notFound('src/views/404.hbs'))
app.use(routes(store))
return Server(app.callback())
diff --git a/src/style/components/main-menu.scss b/src/style/components/main-menu.scss
index e050f28b..fff6906d 100644
--- a/src/style/components/main-menu.scss
+++ b/src/style/components/main-menu.scss
@@ -7,11 +7,30 @@
list-style: none;
- li {
+ .main-menu__submenu {
+ background-color: rgba(255, 255, 255, 0.95);
+
+ display: block;
+ position: absolute;
+ top: 100%;
+ left: -1em;
+
+ padding: 0 0.5em 0.5em;
+
+ & > li {
+ display: block;
+ padding: 0.3em 0.2em 0.5em;
+ margin: 0 0.5em;
+ }
+ }
+
+ & > li {
display: inline-block;
padding: 0.3em 0.2em 0.5em;
margin: 0 0.5em;
+ position: relative;
+
&:first-child {
margin-left: 1em;
}
diff --git a/src/style/footer.scss b/src/style/footer.scss
index 9159a00c..31e964fa 100644
--- a/src/style/footer.scss
+++ b/src/style/footer.scss
@@ -36,4 +36,13 @@ footer {
box-sizing: padding-box;
}
}
+
+ &.footer--centered {
+ .copyright {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ justify-content: center;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/views/404.hbs b/src/views/404.hbs
new file mode 100644
index 00000000..b19b31af
--- /dev/null
+++ b/src/views/404.hbs
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ По вашему запросу ничего не найдено — Шлиссельбургская ДХШ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/main.hbs b/src/views/main.hbs
new file mode 100644
index 00000000..6abcb2cf
--- /dev/null
+++ b/src/views/main.hbs
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Шлиссельбургская Детская Художественная Школа
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{{content}}}
+
+
+
+
+