Serverside Teachers
This commit is contained in:
1 parent
f257e08e7c
commit
525f630812
11 files changed
+275
-87
No files matched your search
@@ -93,29 +93,6 @@ app.config(($locationProvider, $routeProvider) => {
|
|||||||
.then(i => i.data)
|
.then(i => i.data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: '/collective',
|
|
||||||
template: '<page-collective members="$resolve.list"></page-collective>',
|
|
||||||
title: 'Преподаватели',
|
|
||||||
resolve: {
|
|
||||||
list: api => api.teacher
|
|
||||||
.list(collectiveSortPattern)
|
|
||||||
.then(i => i.data)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '/teacher/:id',
|
|
||||||
template: '<page-teacher member="$resolve.profile"></page-teacher>',
|
|
||||||
title: 'Преподаватели',
|
|
||||||
resolve: {
|
|
||||||
profile: ($route, api) => {
|
|
||||||
let id = $route.current.params.id
|
|
||||||
return api.teacher
|
|
||||||
.fetch(id)
|
|
||||||
.then(i => i.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: '/awards',
|
name: '/awards',
|
||||||
template: '<page-awards items="$resolve.awards"></page-awards>',
|
template: '<page-awards items="$resolve.awards"></page-awards>',
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const React = require('react')
|
||||||
|
|
||||||
|
const Share = () => (
|
||||||
|
<div className="likely">
|
||||||
|
<div className="vkontakte">Поделиться</div>
|
||||||
|
<div className="facebook">Поделиться</div>
|
||||||
|
<div className="telegram">Отправить</div>
|
||||||
|
<div className="twitter">Твитнуть</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exports = Share
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
const React = require('react')
|
||||||
|
const Share = require('./Share')
|
||||||
|
const getHtml = require('../lib/component').getHtml
|
||||||
|
|
||||||
|
const Head = ({url, children}) => (
|
||||||
|
!url
|
||||||
|
? <h1>{children}</h1>
|
||||||
|
: <a href={url}><h2>{children}</h2></a>
|
||||||
|
)
|
||||||
|
|
||||||
|
const TeacherProfile = ({picture, name, url, profile, biography, shareable}) => (
|
||||||
|
<div className="teacher-profile">
|
||||||
|
<div className="teacher-profile__picture">
|
||||||
|
<img src={picture.url}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="teacher-profile__info">
|
||||||
|
<Head url={url}>
|
||||||
|
<span>{name[0]} {name[1]}</span> <strong>{name[2]}</strong>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="teacher-profile__position"><i><strong>{profile.position}</strong></i></p>
|
||||||
|
<p>{profile.status}</p>
|
||||||
|
<p>Образование: {profile.edu}</p>
|
||||||
|
<p>Диплом: <i>{profile.diploma}</i></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="teacher-profile__biography">
|
||||||
|
{getHtml(biography)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!shareable ? null : (
|
||||||
|
<Share/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exports = TeacherProfile
|
||||||
@@ -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
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
const Data = require('./Data')
|
const Data = require('./Data')
|
||||||
const Album = require('./Album')
|
const Album = require('./Album')
|
||||||
|
const Teacher = require('./Teacher')
|
||||||
const Image = require('./Image')
|
const Image = require('./Image')
|
||||||
|
|
||||||
const {MongoClient, ObjectId} = require('mongodb')
|
const {MongoClient, ObjectId} = require('mongodb')
|
||||||
@@ -28,6 +29,7 @@ export function id(i) {
|
|||||||
function init(db) {
|
function init(db) {
|
||||||
Data.setStore(Album, db.collection('albums'))
|
Data.setStore(Album, db.collection('albums'))
|
||||||
Data.setStore(Image, db.collection('images'))
|
Data.setStore(Image, db.collection('images'))
|
||||||
|
Data.setStore(Teacher, db.collection('collective'))
|
||||||
|
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
+7
-4
@@ -40,9 +40,12 @@ export default [
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'text': 'Коллектив',
|
text: 'Коллектив',
|
||||||
'url': '/collective',
|
url: '/collective',
|
||||||
'color': 'pink'
|
color: 'pink',
|
||||||
|
items: [
|
||||||
|
{url: '/teacher/*', text: ''},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Галерея',
|
text: 'Галерея',
|
||||||
@@ -66,7 +69,7 @@ export default [
|
|||||||
{url: 'structure', text: 'Структура и органы управления образовательной организацией'},
|
{url: 'structure', text: 'Структура и органы управления образовательной организацией'},
|
||||||
{url: 'education', text: 'Образование'},
|
{url: 'education', text: 'Образование'},
|
||||||
{url: 'standards', text: 'Образовательные стандарты'},
|
{url: 'standards', text: 'Образовательные стандарты'},
|
||||||
{url: '/collective', text: 'Руководство. Педагогический (научно-педагогический) состав'},
|
//{url: '/collective', text: 'Руководство. Педагогический (научно-педагогический) состав'},
|
||||||
{url: 'support', text: 'Материально-техническое обеспечение образовательного процесса'},
|
{url: 'support', text: 'Материально-техническое обеспечение образовательного процесса'},
|
||||||
{url: 'grants', text: 'Стипендии и иные виды материальной поддержки'},
|
{url: 'grants', text: 'Стипендии и иные виды материальной поддержки'},
|
||||||
{url: 'paid-services', text: 'Платные образовательные услуги'},
|
{url: 'paid-services', text: 'Платные образовательные услуги'},
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {index as indexFile} from '../config'
|
|||||||
import sitemap from './sitemap'
|
import sitemap from './sitemap'
|
||||||
import schedule from './schedule'
|
import schedule from './schedule'
|
||||||
import documents from './documents'
|
import documents from './documents'
|
||||||
import teachers from './teachers'
|
|
||||||
import pages from './pages'
|
import pages from './pages'
|
||||||
|
|
||||||
export function routes(store) {
|
export function routes(store) {
|
||||||
@@ -16,7 +15,6 @@ export function routes(store) {
|
|||||||
sitemap(),
|
sitemap(),
|
||||||
schedule(),
|
schedule(),
|
||||||
documents(store),
|
documents(store),
|
||||||
teachers(),
|
|
||||||
pages(),
|
pages(),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
+90
-55
@@ -1,65 +1,100 @@
|
|||||||
import co from 'co';
|
const React = require('react')
|
||||||
import compose from 'koa-compose';
|
const Article = require('../components/Article')
|
||||||
import route from 'koa-route';
|
const TeacherProfile = require('../components/TeacherProfile')
|
||||||
import {index, json} from './';
|
const Teacher = require('../core/Teacher')
|
||||||
import {sortByPattern} from '../utils/sort';
|
const ImageArtifactType = require('../core/ImageArtifactType')
|
||||||
import {c} from '../core/db';
|
const {render} = require('../lib/render')
|
||||||
|
const {prioritySort} = require('../lib/sort')
|
||||||
|
const getPathWithNoTrailingSlash = require('../lib/url').getPathWithNoTrailingSlash
|
||||||
|
|
||||||
let split = d => s => s.split(d);
|
const {get} = require('koa-route')
|
||||||
let toArray = split(',');
|
|
||||||
|
|
||||||
export default function () {
|
function getCollectiveMeta() {
|
||||||
return compose([
|
return {
|
||||||
collective(),
|
title: 'Преподаватели',
|
||||||
list(),
|
description: 'Преподаватели Шлиссельбургской ДХШ',
|
||||||
teacher()
|
}
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
function collective() {
|
|
||||||
return route.get('/collective', index());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function list() {
|
function getMeta(teacher) {
|
||||||
return route.get('/teacher/list', json(
|
return {
|
||||||
async(ctx) => {
|
title: teacher.name,
|
||||||
ctx.type = 'application/json';
|
description: 'Преподаватель Шлиссельбургской ДХШ',
|
||||||
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 teacher() {
|
const pictureTypes = [
|
||||||
return route.get('/teacher/:id', json(
|
ImageArtifactType.BIG,
|
||||||
async (ctx, id) => {
|
ImageArtifactType.MEDIUM,
|
||||||
ctx.type = 'application/json';
|
ImageArtifactType.ORIGIN,
|
||||||
|
]
|
||||||
|
|
||||||
let data = await c('collective').findOne({id: id});
|
const profilePicture = profile => profile.picture.findArtifact(pictureTypes)
|
||||||
if (!data) {
|
|
||||||
ctx.status = 404;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = (
|
||||||
|
<div className="page-collective content content_full">
|
||||||
|
<img className="page-collective__collective-image"
|
||||||
|
width="100%"
|
||||||
|
src="https://static.shlisselburg.org/art/graphics/collective.jpg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="content content_semi-wide">
|
||||||
|
{teachers.map((teacher, index) => (
|
||||||
|
<TeacherProfile key={index}
|
||||||
|
profile={teacher}
|
||||||
|
picture={profilePicture(teacher)}
|
||||||
|
url={teacher.url}
|
||||||
|
biography={teacher.biography}
|
||||||
|
name={teacher.splitName()}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.type = 'text/html'
|
||||||
|
ctx.body = await render(path, Component, getCollectiveMeta(), {menuPadding: false})
|
||||||
|
} else {
|
||||||
|
ctx.status = 404
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let processProfile = profile => co(function *() {
|
function getTeacher() {
|
||||||
let imageId = profile.picture;
|
return get('/teacher/:id', async (ctx, id) => {
|
||||||
profile.picture = yield c('images').findOne({_id: imageId});
|
const path = getPathWithNoTrailingSlash(ctx.path)
|
||||||
profile.url = `/teacher/${profile.id}`;
|
const teacher = await Teacher.findById(id)
|
||||||
return profile;
|
|
||||||
});
|
if (teacher) {
|
||||||
|
const Component = (
|
||||||
|
<div className="content content_semi-wide">
|
||||||
|
<TeacherProfile profile={teacher}
|
||||||
|
picture={profilePicture(teacher)}
|
||||||
|
biography={teacher.biography}
|
||||||
|
name={teacher.splitName()}
|
||||||
|
shareable={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.type = 'text/html'
|
||||||
|
ctx.body = await render(path, Component, getMeta(teacher), {commentsEnabled: true})
|
||||||
|
} else {
|
||||||
|
ctx.status = 404
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getCollective = getCollective
|
||||||
|
exports.getTeacher = getTeacher
|
||||||
@@ -24,12 +24,22 @@ const home = require('./routes/home')
|
|||||||
const article = require('./routes/articles')
|
const article = require('./routes/articles')
|
||||||
const albums = require('./routes/albums')
|
const albums = require('./routes/albums')
|
||||||
const gallery = require('./routes/gallery')
|
const gallery = require('./routes/gallery')
|
||||||
|
const teachers = require('./routes/teachers')
|
||||||
|
|
||||||
const dirPublic = path.join(__dirname, '../public')
|
const dirPublic = path.join(__dirname, '../public')
|
||||||
const dirTemplates = path.join(__dirname, 'templates')
|
const dirTemplates = path.join(__dirname, 'templates')
|
||||||
|
|
||||||
handlebars.registerHelper('raw-helper', options => options.fn())
|
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) {
|
export default function (store) {
|
||||||
const $ = convert
|
const $ = convert
|
||||||
|
|
||||||
@@ -58,6 +68,8 @@ export default function (store) {
|
|||||||
app.use(article.getArticles(5))
|
app.use(article.getArticles(5))
|
||||||
app.use(article.getArticle())
|
app.use(article.getArticle())
|
||||||
app.use(albums.getAlbum())
|
app.use(albums.getAlbum())
|
||||||
|
app.use(teachers.getCollective(collectiveOrder))
|
||||||
|
app.use(teachers.getTeacher())
|
||||||
app.use(routes(store))
|
app.use(routes(store))
|
||||||
|
|
||||||
return Server(app.callback())
|
return Server(app.callback())
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ $picture-width: 30%;
|
|||||||
$info-width: 100% - $picture-width;
|
$info-width: 100% - $picture-width;
|
||||||
|
|
||||||
.teacher-profile {
|
.teacher-profile {
|
||||||
margin: 0 auto $double-margin;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
&__picture {
|
&__picture {
|
||||||
|
margin: 0 0 $double-margin;
|
||||||
width: $picture-width;
|
width: $picture-width;
|
||||||
max-width: 15em;
|
max-width: 15em;
|
||||||
min-width: 12em;
|
min-width: 12em;
|
||||||
@@ -23,7 +22,8 @@ $info-width: 100% - $picture-width;
|
|||||||
&__info {
|
&__info {
|
||||||
width: $info-width;
|
width: $info-width;
|
||||||
|
|
||||||
padding-left: $single-margin;
|
padding-left: $double-margin;
|
||||||
|
margin: 0 0 $double-margin;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
|||||||
Reference in new issue
Block a user