From 525f63081254204ff30772c33fe5443e45fda681 Mon Sep 17 00:00:00 2001 From: Roman Timashev Date: Fri, 26 May 2017 01:38:49 +0300 Subject: [PATCH] Serverside Teachers --- src/client/app.js | 23 ---- src/components/Share.js | 12 ++ src/components/TeacherProfile.js | 40 ++++++ src/core/Teacher.js | 72 ++++++++++ src/core/db.js | 2 + src/lib/sort.js | 37 ++++++ src/models/menu.js | 11 +- src/routes/index.js | 2 - src/routes/teachers.js | 145 +++++++++++++-------- src/server.js | 12 ++ src/style/components/teacher-proifile.scss | 6 +- 11 files changed, 275 insertions(+), 87 deletions(-) create mode 100644 src/components/Share.js create mode 100644 src/components/TeacherProfile.js create mode 100644 src/core/Teacher.js create mode 100644 src/lib/sort.js diff --git a/src/client/app.js b/src/client/app.js index 5e6dd6d7..907d3bed 100644 --- a/src/client/app.js +++ b/src/client/app.js @@ -93,29 +93,6 @@ app.config(($locationProvider, $routeProvider) => { .then(i => i.data) } }, - { - name: '/collective', - template: '', - title: 'Преподаватели', - resolve: { - list: api => api.teacher - .list(collectiveSortPattern) - .then(i => i.data) - } - }, - { - name: '/teacher/:id', - template: '', - title: 'Преподаватели', - resolve: { - profile: ($route, api) => { - let id = $route.current.params.id - return api.teacher - .fetch(id) - .then(i => i.data) - } - } - }, { name: '/awards', template: '', diff --git a/src/components/Share.js b/src/components/Share.js new file mode 100644 index 00000000..5489c466 --- /dev/null +++ b/src/components/Share.js @@ -0,0 +1,12 @@ +const React = require('react') + +const Share = () => ( +
+
Поделиться
+
Поделиться
+
Отправить
+
Твитнуть
+
+) + +module.exports = Share diff --git a/src/components/TeacherProfile.js b/src/components/TeacherProfile.js new file mode 100644 index 00000000..9080847c --- /dev/null +++ b/src/components/TeacherProfile.js @@ -0,0 +1,40 @@ +const React = require('react') +const Share = require('./Share') +const getHtml = require('../lib/component').getHtml + +const Head = ({url, children}) => ( + !url + ?

{children}

+ :

{children}

+) + +const TeacherProfile = ({picture, name, url, profile, biography, shareable}) => ( +
+
+ +
+ +
+ + {name[0]} {name[1]} {name[2]} + + +
+

{profile.position}

+

{profile.status}

+

Образование: {profile.edu}

+

Диплом: {profile.diploma}

+
+ +
+ {getHtml(biography)} +
+
+ + {!shareable ? null : ( + + )} +
+) + +module.exports = TeacherProfile diff --git a/src/core/Teacher.js b/src/core/Teacher.js new file mode 100644 index 00000000..cdb5b652 --- /dev/null +++ b/src/core/Teacher.js @@ -0,0 +1,72 @@ +const Data = require('./Data') +const Image = require('./Image') +const {find, findOne} = require('../lib/store') + +const store = () => Data.getStore(Teacher) + +class Teacher { + static async find(query, options = {}) { + const items = await find(store(), query, options) + + return Promise.all(items.map(processProfile)) + } + + static async findById(id) { + const data = await findOne(store(), {id}) + if (!data) return null + + return processProfile(data) + } + + constructor(data) { + this.id = data.id + this.biography = data.biography + this.file = data.file + this.edu = data.edu + this.picture = data.picture + this.diploma = data.diploma + this.position = data.position + this.name = data.name + this.status = data.status + this.hash = data.hash + + this.url = `/teacher/${this.id}` + } + + /** + * + * "First Middle Last" -> "F. M. Last" + * + * @returns {string} + */ + shortName() { + const f = i => i.replace(/^(.).+/, '$1') + const [first, middle, last] = this.splitName() + + return `${f(first)}. ${f(middle)}. ${last}` + } + + /** + * + * "First Middle Last" -> ["First", "Middle", "Last"] + * + * @returns {Array|{index: number, input: string}} + */ + splitName() { + const name = this.name + const m = /(.+)\s(.+)\s(.+)/.exec(name) + return m.slice(1) + } +} + +const processProfile = async profile => { + const imageId = profile.picture + const picture = await Image.findById(imageId) + + return new Teacher({ + ...profile, + picture, + }) +} + +module.exports = Teacher diff --git a/src/core/db.js b/src/core/db.js index 08ce00fb..ae645b73 100644 --- a/src/core/db.js +++ b/src/core/db.js @@ -1,5 +1,6 @@ const Data = require('./Data') const Album = require('./Album') +const Teacher = require('./Teacher') const Image = require('./Image') const {MongoClient, ObjectId} = require('mongodb') @@ -28,6 +29,7 @@ export function id(i) { function init(db) { Data.setStore(Album, db.collection('albums')) Data.setStore(Image, db.collection('images')) + Data.setStore(Teacher, db.collection('collective')) return db } diff --git a/src/lib/sort.js b/src/lib/sort.js new file mode 100644 index 00000000..3fa95c00 --- /dev/null +++ b/src/lib/sort.js @@ -0,0 +1,37 @@ +function prioritySort(priority, key, list) { + priority = priority.reverse() + + let weight = i => { + const p = key(i) + const index = priority.indexOf(p) + return index > -1 + ? index + 1 + : 0 + } + + return sort(list, weight) +} + +function sort(list, fn, reverse = false) { + return list + .map(i => [fn(i), i]) + .sort((a, b) => reverse + ? a[0] - b[0] + : b[0] - a[0]) + .map(i => i[1]) +} + +function sortBy(fn, reverse = false) { + return (i1, i2) => { + const t1 = fn(i1) + const t2 = fn(i2) + + return reverse + ? t1 - t2 + : t2 - t1 + } +} + +exports.prioritySort = prioritySort +exports.sortBy = sortBy +exports.sort = sort diff --git a/src/models/menu.js b/src/models/menu.js index 384b61d9..768a2371 100644 --- a/src/models/menu.js +++ b/src/models/menu.js @@ -40,9 +40,12 @@ export default [ ] }, { - 'text': 'Коллектив', - 'url': '/collective', - 'color': 'pink' + text: 'Коллектив', + url: '/collective', + color: 'pink', + items: [ + {url: '/teacher/*', text: ''}, + ] }, { text: 'Галерея', @@ -66,7 +69,7 @@ export default [ {url: 'structure', text: 'Структура и органы управления образовательной организацией'}, {url: 'education', text: 'Образование'}, {url: 'standards', text: 'Образовательные стандарты'}, - {url: '/collective', text: 'Руководство. Педагогический (научно-педагогический) состав'}, + //{url: '/collective', text: 'Руководство. Педагогический (научно-педагогический) состав'}, {url: 'support', text: 'Материально-техническое обеспечение образовательного процесса'}, {url: 'grants', text: 'Стипендии и иные виды материальной поддержки'}, {url: 'paid-services', text: 'Платные образовательные услуги'}, diff --git a/src/routes/index.js b/src/routes/index.js index 7f77c529..bffd844b 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -8,7 +8,6 @@ import {index as indexFile} from '../config' import sitemap from './sitemap' import schedule from './schedule' import documents from './documents' -import teachers from './teachers' import pages from './pages' export function routes(store) { @@ -16,7 +15,6 @@ export function routes(store) { sitemap(), schedule(), documents(store), - teachers(), pages(), ]) } diff --git a/src/routes/teachers.js b/src/routes/teachers.js index 37f0e125..0a1fdd43 100644 --- a/src/routes/teachers.js +++ b/src/routes/teachers.js @@ -1,65 +1,100 @@ -import co from 'co'; -import compose from 'koa-compose'; -import route from 'koa-route'; -import {index, json} from './'; -import {sortByPattern} from '../utils/sort'; -import {c} from '../core/db'; +const React = require('react') +const Article = require('../components/Article') +const TeacherProfile = require('../components/TeacherProfile') +const Teacher = require('../core/Teacher') +const ImageArtifactType = require('../core/ImageArtifactType') +const {render} = require('../lib/render') +const {prioritySort} = require('../lib/sort') +const getPathWithNoTrailingSlash = require('../lib/url').getPathWithNoTrailingSlash -let split = d => s => s.split(d); -let toArray = split(','); +const {get} = require('koa-route') -export default function () { - return compose([ - collective(), - list(), - teacher() - ]); -}; - -function collective() { - return route.get('/collective', index()); +function getCollectiveMeta() { + return { + title: 'Преподаватели', + description: 'Преподаватели Шлиссельбургской ДХШ', + } } -function list() { - return route.get('/teacher/list', json( - async(ctx) => { - ctx.type = 'application/json'; - let pattern = toArray(ctx.query.sort || ''); - - let data = await c('collective').find().toArray(); - if (!data) { - ctx.status = 404; - return; - } - - let collective = await Promise.all( - data.map(processProfile) - ); - - ctx.body = sortByPattern(collective, pattern, i => i.id); - } - )); +function getMeta(teacher) { + return { + title: teacher.name, + description: 'Преподаватель Шлиссельбургской ДХШ', + } } -function teacher() { - return route.get('/teacher/:id', json( - async (ctx, id) => { - ctx.type = 'application/json'; +const pictureTypes = [ + ImageArtifactType.BIG, + ImageArtifactType.MEDIUM, + ImageArtifactType.ORIGIN, +] - let data = await c('collective').findOne({id: id}); - if (!data) { - ctx.status = 404; - return; - } +const profilePicture = profile => profile.picture.findArtifact(pictureTypes) - ctx.body = await processProfile(data); - } - )) +function getCollective(order) { + const teachersSorted = prioritySort.bind(null, order, t => t.id) + + return get('/collective', async ctx => { + const path = getPathWithNoTrailingSlash(ctx.path) + let teachers = await Teacher.find({}) + + if (teachers) { + teachers = teachersSorted(teachers) + + const Component = ( +
+ + +
+ {teachers.map((teacher, index) => ( + + ))} +
+
+ ) + + ctx.type = 'text/html' + ctx.body = await render(path, Component, getCollectiveMeta(), {menuPadding: false}) + } else { + ctx.status = 404 + } + }) } -let processProfile = profile => co(function *() { - let imageId = profile.picture; - profile.picture = yield c('images').findOne({_id: imageId}); - profile.url = `/teacher/${profile.id}`; - return profile; -}); \ No newline at end of file +function getTeacher() { + return get('/teacher/:id', async (ctx, id) => { + const path = getPathWithNoTrailingSlash(ctx.path) + const teacher = await Teacher.findById(id) + + if (teacher) { + const Component = ( +
+ +
+ ) + + ctx.type = 'text/html' + ctx.body = await render(path, Component, getMeta(teacher), {commentsEnabled: true}) + } else { + ctx.status = 404 + } + }) + +} + +exports.getCollective = getCollective +exports.getTeacher = getTeacher diff --git a/src/server.js b/src/server.js index 88dfa192..e2bbbb67 100644 --- a/src/server.js +++ b/src/server.js @@ -24,12 +24,22 @@ const home = require('./routes/home') const article = require('./routes/articles') const albums = require('./routes/albums') const gallery = require('./routes/gallery') +const teachers = require('./routes/teachers') const dirPublic = path.join(__dirname, '../public') const dirTemplates = path.join(__dirname, 'templates') handlebars.registerHelper('raw-helper', options => options.fn()) +const collectiveOrder = [ + 'mg-timasheva', + 'va-sarzhin', + 'od-gogoleva', + 'my-valkova', + 'vv-voronova', + 'nv-andreeva', +] + export default function (store) { const $ = convert @@ -58,6 +68,8 @@ export default function (store) { app.use(article.getArticles(5)) app.use(article.getArticle()) app.use(albums.getAlbum()) + app.use(teachers.getCollective(collectiveOrder)) + app.use(teachers.getTeacher()) app.use(routes(store)) return Server(app.callback()) diff --git a/src/style/components/teacher-proifile.scss b/src/style/components/teacher-proifile.scss index a854ecc9..ae4b6d9a 100644 --- a/src/style/components/teacher-proifile.scss +++ b/src/style/components/teacher-proifile.scss @@ -4,13 +4,12 @@ $picture-width: 30%; $info-width: 100% - $picture-width; .teacher-profile { - margin: 0 auto $double-margin; - display: flex; justify-content: space-between; flex-wrap: wrap; &__picture { + margin: 0 0 $double-margin; width: $picture-width; max-width: 15em; min-width: 12em; @@ -23,7 +22,8 @@ $info-width: 100% - $picture-width; &__info { width: $info-width; - padding-left: $single-margin; + padding-left: $double-margin; + margin: 0 0 $double-margin; box-sizing: border-box; h2 {