diff --git a/src/components/App.js b/src/components/App.js
index 51b72d21..c649f54b 100644
--- a/src/components/App.js
+++ b/src/components/App.js
@@ -13,10 +13,8 @@ const App = ({children, menu}) => (
-
diff --git a/src/components/Article.js b/src/components/Article.js
new file mode 100644
index 00000000..d07550f7
--- /dev/null
+++ b/src/components/Article.js
@@ -0,0 +1,24 @@
+const React = require('react')
+const getHtml = require('../lib/component').getHtml
+const dateFormat = require('../lib/date').dateFormat
+
+const Head = ({url, children}) => (
+ !url
+ ?
{children}
+ : {children}
+)
+
+const Article = ({data, url, title, date}) => (
+
+
+ {title}
+ {dateFormat(date)}
+
+
+
+ {getHtml(data)}
+
+
+)
+
+module.exports = Article
diff --git a/src/components/ArticleList.js b/src/components/ArticleList.js
new file mode 100644
index 00000000..af3ebb56
--- /dev/null
+++ b/src/components/ArticleList.js
@@ -0,0 +1,27 @@
+const React = require('react')
+const Paginator = require('./Paginator')
+const Article = require('./Article')
+
+const ArticleList = ({articles, nextPage, prevPage}) => (
+
+ {!prevPage ? null : (
+
+ )}
+
+ {articles.map(article => (
+
+ ))}
+
+ {!nextPage ? null : (
+
+ )}
+
+)
+
+module.exports = ArticleList
diff --git a/src/components/Menu.js b/src/components/Menu.js
index 5438513f..80510006 100644
--- a/src/components/Menu.js
+++ b/src/components/Menu.js
@@ -1,20 +1,24 @@
const React = require('react')
+const getPathWithNoTrailingSlas = require('../lib/url').getPathWithNoTrailingSlash
const hasSubmenu = item => !!item.items
const doDrawSubmenu = item => false //item.active && hasSubmenu(item)
+const itemUrl = url => getPathWithNoTrailingSlas(url)
-const selectedClassName = active => active
+const selectedClassName = flag => flag
? 'selected'
: ''
const MenuItem = ({active, text, url}) => (active
? {text}
- : {text}
+ : {text}
)
const Menu = ({items}) => {
const content = items.map((item, index) => (
-
+
{!doDrawSubmenu(item) ? null :
diff --git a/src/components/Page.js b/src/components/Page.js
new file mode 100644
index 00000000..1e9170d5
--- /dev/null
+++ b/src/components/Page.js
@@ -0,0 +1,12 @@
+const React = require('react')
+const getHtml = require('../lib/component').getHtml
+
+const Page = ({children}) => (
+
+
+ {getHtml(children)}
+
+
+)
+
+module.exports = Page
diff --git a/src/components/Paginator.js b/src/components/Paginator.js
new file mode 100644
index 00000000..be6d438f
--- /dev/null
+++ b/src/components/Paginator.js
@@ -0,0 +1,16 @@
+const React = require('react')
+
+const text = ['↑ Предыдущая страница', '↓ Следующая страница']
+const style = ['article-page-control__top', 'article-page-control__bottom']
+
+const i = type => type === 'top'
+ ? 0
+ : 1
+
+const Paginator = ({url, type}) => (
+
+)
+
+module.exports = Paginator
diff --git a/src/lib/component.js b/src/lib/component.js
index 8a2dcd47..54ba3de1 100644
--- a/src/lib/component.js
+++ b/src/lib/component.js
@@ -3,13 +3,20 @@ import {renderToStaticMarkup} from 'react-dom/server'
const App = require('../components/App')
-function renderApp({menu, page}) {
+function renderApp({menu, component}) {
const app = (
-
+ {component}
)
return renderToStaticMarkup(app)
}
+function getHtml(html) {
+ return (
+
+ )
+}
+
exports.renderApp = renderApp
+exports.getHtml = getHtml
diff --git a/src/lib/render.js b/src/lib/render.js
new file mode 100644
index 00000000..95d42f97
--- /dev/null
+++ b/src/lib/render.js
@@ -0,0 +1,91 @@
+const {readFile} = require('async-file')
+const handlebars = require('handlebars')
+
+const url = require('url')
+const renderApp = require('../lib/component').renderApp
+const getHtml = require('../lib/component').getHtml
+const getPathWithNoTrailingSlash = require('../lib/url').getPathWithNoTrailingSlash
+const urlToPattern = require('../lib/url').urlToPattern
+const isMatchPathPattern = require('../lib/url').isMatchPathPattern
+const isEqualPate = require('../lib/url').isEqualPath
+const {compose, any} = require('../lib/common')
+
+const menuModel = require('../models/menu').default
+const config = require('../config')
+
+function isActive(path, menuItem) {
+ return isEqualPate(path, menuItem.url, true)
+}
+
+function isHighlighted(path, menuItem) {
+ return any(flatMenuItem(menuItem)
+ .map(i => isMatchPathPattern(
+ compose(urlToPattern, getPathWithNoTrailingSlash)(i.url),
+ path,
+ 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),
+ highlighted: isHighlighted(path, item),
+ }))
+ return {items}
+}
+
+const getTitle = meta => 'title' in meta
+ ? meta.title
+ : 'Шлиссельбургская художественная школа'
+
+const isHtml = data => typeof data === 'string'
+
+async function render(path, data, meta) {
+ const component = isHtml(data)
+ ? getHtml(data)
+ : data
+
+ const menu = buildMenu(path, menuModel)
+ const content = renderApp({menu, component})
+
+ const source = await readFile(config.viewMain, 'utf-8')
+ const template = handlebars.compile(source)
+
+ const metaData = {
+ ...meta,
+ description: 'Сайт Шлиссельбургской художественной школы',
+ image: 'https://art.shlisselburg.org/entrance.jpg',
+ url: `https://art.shlisselburg.org${path}`,
+ }
+
+ return template({
+ content,
+ title: getTitle(meta),
+ meta: metaData,
+ })
+}
+
+exports.render = render
diff --git a/src/lib/url.js b/src/lib/url.js
index 1b2c3d4a..0452ab69 100644
--- a/src/lib/url.js
+++ b/src/lib/url.js
@@ -1,7 +1,10 @@
function getPathWithNoTrailingSlash(path) {
- return path.charAt(path.length - 1) === '/'
+ path = path.charAt(path.length - 1) === '/'
? path.substr(0, path.length - 1)
: path
+ return path.length === 0
+ ? '/'
+ : path
}
function isEqualPath(a, b, ignoreTrailingSlash = false) {
@@ -10,5 +13,28 @@ function isEqualPath(a, b, ignoreTrailingSlash = false) {
: a === b
}
+function isMatchPathPattern(pattern, path, ignoreTrailingSlash = false) {
+ const test = ignoreTrailingSlash
+ ? getPathWithNoTrailingSlash(path)
+ : path
+ return pattern.test(test)
+}
+
+/**
+ *
+ * /article/* -> ^\/article\/[/\w\d-_]+\/?$
+ *
+ * @param url
+ * @return {RegExp}
+ */
+function urlToPattern(url) {
+ const u = url.replace('/*', '[/\\w\\d-_]+')
+ const pattern = `^${u}/?$`
+
+ return new RegExp(pattern)
+}
+
exports.getPathWithNoTrailingSlash = getPathWithNoTrailingSlash
+exports.isMatchPathPattern = isMatchPathPattern
+exports.urlToPattern = urlToPattern
exports.isEqualPath = isEqualPath
diff --git a/src/models/menu.js b/src/models/menu.js
index ed542083..ebc9e1c7 100644
--- a/src/models/menu.js
+++ b/src/models/menu.js
@@ -5,13 +5,10 @@ export default [
color: 'blue',
items: [
{url: '/summer-school/2017', text: 'Летние каникулы 2017'},
+ {url: '/articles/*', text: 'Статьи'},
+ {url: '/article/*', text: ''},
],
- },
- {
- 'text': 'События',
- 'url': '/events',
- 'color': 'orange'
- },
+ }, ,
{
'text': 'Расписание',
'url': '/schedule',
@@ -60,7 +57,7 @@ export default [
{
url: '/info/',
text: 'Сведения об образовательной организации',
- color: 'gray',
+ color: 'orange',
items: [
{url: 'main', text: 'Основные сведения'},
{url: 'structure', text: 'Структура и органы управления образовательной организацией'},
@@ -75,6 +72,8 @@ export default [
{url: 'tools', text: 'Список рекомендованных материалов'},
{url: 'history', text: 'История школы'},
+
+ {url: '*', text: ''},
]
},
{
diff --git a/src/routes/articles.js b/src/routes/articles.js
index 0af181b1..d09f5e41 100644
--- a/src/routes/articles.js
+++ b/src/routes/articles.js
@@ -1,28 +1,136 @@
-import route from 'koa-route';
-import {c} from '../core/db';
-import {accepts, index} from './';
+const React = require('react')
+import {get} from 'koa-route'
+import {c} from '../core/db'
+const Article = require('../components/Article')
+const ArticleList = require('../components/ArticleList')
+const Paginator = require('../components/Paginator')
+const {render} = require('../lib/render')
+const getPathWithNoTrailingSlash = require('../lib/url').getPathWithNoTrailingSlash
+const timestamp = require('../lib/date').timestamp
+const {sortBy} = require('../utils/sort')
+const accepts = require('./index').accepts
-async function exists(ctx, id) {
- let i = await article(id);
- return i ? true : false;
+const sortArticleByDate = sortBy(
+ i => timestamp(new Date(i.date))
+)
+
+const articleUrl = article => `/article/${article.id}`
+
+async function findArticle(id) {
+ return c('articles').findOne({id: id})
}
-async function article(id){
- let i = await c('events').findOne({id: id});
- if (!i) {
- i = await c('timeline').findOne({id: id});
- }
- return i;
+async function findArticlesNin(nin, skip, limit, sort) {
+ return c('articles')
+ .find({
+ _id: {$nin: nin}
+ })
+ .sort(sort)
+ .skip(skip)
+ .limit(limit)
+ .toArray()
}
-export default function () {
- return route.get('/article/:id', accepts({
- 'text/html': index(exists),
- 'text/plain': index(exists),
- 'application/json': async (ctx, id) => {
- let i = await article(id);
- if (!i) ctx.status = 404;
- else ctx.body = i;
- }
- }))
-};
+async function totalArticles() {
+ return c('articles')
+ .find({})
+ .count()
+}
+
+async function findPinned(page) {
+ const now = new Date()
+ return page !== 1
+ ? []
+ : await c('articles')
+ .find({until: {$gte: now}})
+ .sort({date: -1})
+ .toArray()
+}
+
+function getMeta(article) {
+ return {
+ title: article.title,
+ }
+}
+
+function getArticles(pageSize) {
+ return get('/articles/:page?', async (ctx, page) => {
+ const path = getPathWithNoTrailingSlash(ctx.path)
+ page = parseInt(page) || 1
+
+ const limit = pageSize
+ const skip = (page - 1) * pageSize
+
+ const pinnedArticles = await findPinned(page)
+ const total = await totalArticles()
+ const totalPages = total / pageSize
+
+ const id = i => i._id
+ const pinnedIds = pinnedArticles.map(id)
+ const articles = await findArticlesNin(pinnedIds, skip, limit, {date: -1})
+
+ const prevPage = page > 1
+ ? page - 1
+ : null
+
+ const nextPage = page < totalPages
+ ? page + 1
+ : null
+
+ const content = [
+ ...pinnedArticles.sort(sortArticleByDate),
+ ...articles
+ ]
+ .map(article => ({
+ ...article,
+ url: articleUrl(article),
+ }))
+ const Component = (
+
+ )
+
+ ctx.body = await render(path, Component, getMeta({title: `Статьи`}))
+ })
+}
+
+function getArticle() {
+ return get('/article/:id', accepts({
+ 'text/html': async (ctx, id) => {
+ const path = getPathWithNoTrailingSlash(ctx.path)
+ const article = await findArticle(id)
+
+ if (article) {
+ const Component = (
+
+ )
+
+ ctx.type = 'text/html'
+ ctx.body = await render(path, Component, getMeta(article))
+ } else {
+ ctx.status = 404
+ }
+ },
+ 'application/json': async (ctx, id) => {
+ const article = await findArticle(id)
+
+ if (article) {
+ ctx.body = article
+ } else {
+ ctx.status = 404
+ }
+ }
+ }))
+}
+
+exports.getArticles = getArticles
+exports.getArticle = getArticle
diff --git a/src/routes/home.js b/src/routes/home.js
new file mode 100644
index 00000000..b907080d
--- /dev/null
+++ b/src/routes/home.js
@@ -0,0 +1,88 @@
+const React = require('react')
+const Article = require('../components/Article')
+const {get} = require('koa-route')
+const {c} = require('../core/db')
+const {render} = require('../lib/render')
+const getPathWithNoTrailingSlash = require('../lib/url').getPathWithNoTrailingSlash
+const timestamp = require('../lib/date').timestamp
+const {sortBy} = require('../utils/sort')
+
+const sortArticleByDate = sortBy(
+ i => timestamp(new Date(i.date))
+)
+
+const articleUrl = article => `/article/${article.id}`
+
+async function findArticlesNin(nin, skip, limit, sort) {
+ return c('articles')
+ .find({
+ _id: {$nin: nin}
+ })
+ .sort(sort)
+ .skip(skip)
+ .limit(limit)
+ .toArray()
+}
+
+async function findPinned(page) {
+ const now = new Date()
+ return page !== 1
+ ? []
+ : await c('articles')
+ .find({until: {$gte: now}})
+ .sort({date: -1})
+ .toArray()
+}
+
+function getComponent(path, article) {
+ return (
+
+ )
+}
+
+function getMeta(article) {
+ return {
+ title: article.title,
+ }
+}
+
+function getHome(pageSize) {
+ return get('/', async (ctx, page) => {
+ const path = getPathWithNoTrailingSlash(ctx.path) || '/'
+ page = parseInt(page) || 1
+
+ const limit = pageSize
+ const skip = (page - 1) * pageSize
+
+ const pinnedArticles = await findPinned(page)
+
+ const id = i => i._id
+ const pinnedIds = pinnedArticles.map(id)
+ const articles = await findArticlesNin(pinnedIds, skip, limit, {date: -1})
+
+ const content = [
+ ...pinnedArticles.sort(sortArticleByDate),
+ ...articles
+ ]
+ .map(article => getComponent(articleUrl(article), article))
+ const Component = (
+
+
+
МБУДО
+
Шлиссельбургская детская художественная школа
+
+
+ {content}
+
+ )
+
+ ctx.body = await render(path, Component, getMeta({title: 'Шлиссельбургская ДХШ'}))
+ })
+}
+
+exports.getHome = getHome
diff --git a/src/routes/index.js b/src/routes/index.js
index 0e061dd5..0f256536 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -1,6 +1,5 @@
import {readFile} from 'async-file'
import handlebars from 'handlebars'
-import route from 'koa-route'
import compose from 'koa-compose'
import {map, assign} from '../utils/common'
@@ -8,20 +7,15 @@ import {index as indexFile} from '../config'
import sitemap from './sitemap'
import schedule from './schedule'
-import {getPageUrls} from '../core/pages'
import news from './news'
import gallery from './gallery'
import documents from './documents'
import teachers from './teachers'
import events from './events'
-import articles from './articles'
import pages from './pages'
-let main = () => route.get('/', index())
-
export function routes(store) {
return compose([
- main(),
sitemap(),
schedule(),
news(),
@@ -29,7 +23,6 @@ export function routes(store) {
documents(store),
teachers(),
events(),
- articles(),
pages(),
])
}
diff --git a/src/routes/pages.js b/src/routes/pages.js
index b2393d0e..5845dc67 100644
--- a/src/routes/pages.js
+++ b/src/routes/pages.js
@@ -1,55 +1,25 @@
-const {readFile} = require('async-file')
-const handlebars = require('handlebars')
+const React = require('react')
+const Page = require('../components/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 {render} = require('../lib/render')
-const menuModel = require('../models/menu').default
-const config = require('../config')
async function getPage(path) {
return await c('pages').findOne({url: path})
}
-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)
- }))
+function getPageMeta(page) {
+ return {
+ title: page.title,
}
-
- 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}
+function getComponent(page) {
+ return {page.data}
}
module.exports = function () {
@@ -57,22 +27,12 @@ module.exports = function () {
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(config.viewMain, 'utf-8')
- const template = handlebars.compile(source)
-
- const title = page.title
- const meta = {
- title,
- description: title,
- image: 'https://art.shlisselburg.org/entrance.jpg',
- url: `https://art.shlisselburg.org${path}`,
- }
-
ctx.type = 'text/html'
- ctx.body = template({content, title, meta})
+ ctx.body = await render(
+ path,
+ getComponent(page),
+ getPageMeta(page),
+ )
} else {
ctx.status = 404
}
diff --git a/src/server.js b/src/server.js
index 10082d4f..52d5ded3 100644
--- a/src/server.js
+++ b/src/server.js
@@ -21,6 +21,8 @@ import handlebars from 'handlebars'
const config = require('./config')
const error = require('./middlewares/error')
+const home = require('./routes/home')
+const article = require('./routes/articles')
const dirPublic = path.join(__dirname, '../public')
const dirTemplates = path.join(__dirname, 'templates')
@@ -52,6 +54,9 @@ export default function (store) {
app.use(queryObject())
app.use(error.notFound(config.view404))
+ app.use(home.getHome(5))
+ app.use(article.getArticles(5))
+ app.use(article.getArticle())
app.use(routes(store))
return Server(app.callback())
diff --git a/src/style/components/article.scss b/src/style/components/article.scss
index 021b1f3a..93662ede 100644
--- a/src/style/components/article.scss
+++ b/src/style/components/article.scss
@@ -6,9 +6,15 @@
&__head {
margin-bottom: 1.25em;
- h3 {
+ h1 {
font-size: $biggest-font-size;
text-align: center;
+ margin-bottom: $half-margin;
+ }
+
+ h2 {
+ font-size: $big-font-size;
+ text-align: center;
}
.date {
diff --git a/src/style/components/main-menu.scss b/src/style/components/main-menu.scss
index fff6906d..0d06b012 100644
--- a/src/style/components/main-menu.scss
+++ b/src/style/components/main-menu.scss
@@ -49,12 +49,6 @@
font-weight: bold;
border-top: $menu-colored-mark-thickness solid $light-color;
- a:link, a:visited {
- color: $dark-color;
- text-decoration: none;
- border-bottom: none;
- }
-
&.blue {
border-top: $menu-colored-mark-thickness solid $blue-color;
}
diff --git a/src/style/main-image.scss b/src/style/main-image.scss
index eb6af9b4..6b7bf1c7 100644
--- a/src/style/main-image.scss
+++ b/src/style/main-image.scss
@@ -1,20 +1,23 @@
@import "variables";
.main-image{
+ display: none;
+
width: 100%;
- display: flex;
- justify-content: center;
- overflow: hidden;
+
+ //display: flex;
+ //justify-content: center;
+ //overflow: hidden;
img {
width: 100%;
max-width: 1280px;
}
- &__content {
- min-width: 1280px;
- width: 1280px;
- }
+ //&__content {
+ // min-width: 1280px;
+ // width: 1280px;
+ //}
}
@media (max-width: $mobile-width) {