diff --git a/.node-version b/.node-version index c966188e..e029aa99 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -4.4.7 +6.8.0 diff --git a/README.md b/README.md index 6e1e9977..18c6fd66 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,61 @@ -# WWW.ART.SHLISSELBURG.ORG -THE SITE OF ART SCHOOL OF **SHLISSELBURG** -Visit [art.shlisselburg.org](https://art.shlisselburg.org) +# [ART.SHLISSELBURG.ORG](https://art.shlisselburg.org) +Art school of **Shlisselburg** -### Used technologies: -- node.js -- koa.js -- angular.js -- mongodb +## Usage -## Contributers -- Roman Timashev [roman@tmshv.ru](mailto:roman@tmshv.ru) +### How to run + +```bash +$ npm start +``` + +### Environments variables + +#### PORT + +TCP Port for app + +```bash +PORT=8080 npm start +``` + +#### PRIVATE + +Path to JSON file with secret keys + +```bash +PRIVATE=private.json npm start +``` + +### How to sync data base + +Local DB to remote DB + +```bash +DB=hudozka +HOST= +USER= +PWD= +mongodump -d ${DB} --gzip --archive | mongorestore -h ${HOST} -u ${USER} -p ${PWD} -d ${DB} --gzip --archive +``` + +## Build client + +```bash +$ NODE_ENV=production gulp deploy +``` + +For developer purposes: + +```bash +$ gulp deploy +``` + +### Based on: +- [NodeJS](http://nodejs.org) 6.0 +- [koa.js](http://koajs.com) 2.0 +- [angular.js](https://angularjs.org) 1.5 +- [MongoDB](http://mongodb.org) 3.2 + +## Contributors +- Roman Timashev ([roman@tmshv.ru](mailto:roman@tmshv.ru)) diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 616134e8..45ff42bf 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -78,7 +78,7 @@ gulp.task('copy templates', () => { gulp.task('compile', () => { const doWatch = !isProduction; - const doSourceMaps = false; + const doSourceMaps = !isProduction; return gulp.src([ './src/client/app.js' diff --git a/modules/hudozka-data/src/DataManager.js b/modules/hudozka-data/src/DataManager.js index d9064701..43f9eb06 100644 --- a/modules/hudozka-data/src/DataManager.js +++ b/modules/hudozka-data/src/DataManager.js @@ -1,7 +1,9 @@ import Timeline from './Timeline'; +import Schedule from './Schedule'; export default class DataManager { constructor(stores) { this.timeline = new Timeline(stores.timeline); + this.schedule = new Schedule(stores.schedules); } } diff --git a/modules/hudozka-data/src/Schedule.js b/modules/hudozka-data/src/Schedule.js new file mode 100644 index 00000000..03dfd2a0 --- /dev/null +++ b/modules/hudozka-data/src/Schedule.js @@ -0,0 +1,48 @@ +import Data from './Data'; + +export default class Schedule extends Data { + + create(){ + + } + + read(){ + + } + + update(){ + + } + + delete(){ + + } + + find(query){ + + } + + add(post) { + let savePost = async(post) => { + const id = post.id; + const type = post.type; + const query = {id: id, type: type}; + + await this.store.update(query, post, {upsert: true}); + let document = await this.store.find(query).limit(1).toArray(); + return document[0]; + }; + + let add = async(post) => { + let document = post; + if(this.save){ + document = await savePost(post); + } + + this.update(document); + return document; + }; + + return add(post); + } +} diff --git a/package.json b/package.json index a62fdb0e..bfb584e9 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "angular": "^1.5.3", "angular-route": "^1.5.3", "angular-spinner": "^0.8.1", - "angulartics": "^1.0.3", + "angulartics": "^1.2.1", "angulartics-google-analytics": "^0.1.4", "babel-core": "^6.7.4", "babel-polyfill": "^6.7.4", @@ -48,6 +48,10 @@ "koa-static": "^3.0.0", "mongodb": "^2.1.14", "mz": "^2.4.0", + "react": "^15.3.2", + "react-dom": "^15.3.2", + "react-redux": "^4.4.5", + "redux": "^3.6.0", "request-promise": "^2.0.1", "sitemap": "^1.5.0", "socket.io": "^1.4.5", @@ -75,10 +79,10 @@ "gulp-uglify": "~1.5.0", "html-minify-loader": "~1.1.0", "istanbul": "~0.4.2", + "mocha": "~2.4.5", "raw-loader": "~0.5.1", "should": "^8.2.2", "vinyl-named": "~1.1.0", - "mocha": "~2.4.5", "webpack-stream": "~3.1.0" } } diff --git a/src/client/app.js b/src/client/app.js index 44935daa..4f26d7f2 100644 --- a/src/client/app.js +++ b/src/client/app.js @@ -210,10 +210,25 @@ app.config(($locationProvider, $routeProvider) => { }); app.run(($location, $rootScope, $http) => { - $rootScope.$on('$routeChangeSuccess', (event, current) => { - let title = current['$$route'].title; - if (title) $rootScope.title = title; - }); + $rootScope.$on('$routeChangeSuccess', (event, current) => { + const url = location.href; + const title = current['$$route'].title; + + if (title) $rootScope.title = title; + + setTimeout(() => { + try { + DISQUS.reset({ + reload: true, + config: function () { + this.page.identifier = url; + this.page.url = url; + } + }); + } catch (e) { + } + }, 500) + }); $rootScope.$on('$routeChangeError', (event, current) => { //let title = current['$$route'].title; diff --git a/src/client/components/PageSchedule.js b/src/client/components/PageSchedule.js index 07ea39e6..3aa6b83d 100644 --- a/src/client/components/PageSchedule.js +++ b/src/client/components/PageSchedule.js @@ -1,46 +1,41 @@ import template from '../../templates/components/page-schedule.html'; import APISchedule from '../api/APISchedule'; -import {populate} from '../../utils/populate'; -import {getCourseNameByID} from '../../models/course'; -import {indexEquals} from '../../utils/common'; -import {select, choise} from '../../utils/common'; export default function (app) { app.component('pageSchedule', { template: template, bindings: { - //currentSchedule: '<', schedules: '<' }, controller: function($http, $location, $routeParams, api, menu) { - this.pageClass = 'page-schedule'; menu.activate('/schedule'); const period = $routeParams.period; const semester = $routeParams.semester; - const useRoute = period && semester; + const useRoute = Boolean(period) && Boolean(semester); - this.loadSchedule = (record, doUpdateURL) => { + const stringPeriod = period => period.join('-'); + + this.loadSchedule = (record, doUpdateUrl) => { let id = record._id; - let scheduleItem = getScheduleByID(id); + let scheduleItem = getScheduleById(id); if (scheduleItem) { - let p = scheduleItem.period; + let p = scheduleItem.period.join('-'); let s = scheduleItem.semester; api.schedule.fetch(p, s, true) .then(i => i.data) .then(i => { - this.schedule = i.schedule; - if (doUpdateURL) $location.url(APISchedule.buildScheduleUrl(p, s)); + this.schedule = i; + if (doUpdateUrl) $location.url(APISchedule.buildScheduleUrl(p, s)); }); } }; - let getScheduleByID = id => this.schedules + const getScheduleById = id => this.schedules .find(i => i._id === id); - let getRoutedSchedule = list => { - return list.find(i => i.period === period && i.semester === semester) - }; + const getSchedule = (period, semester) => this.schedules + .find(i => stringPeriod(i.period) === period && i.semester === semester); let last = list => list.length ? list[list.length - 1] : null; let getDefaultSchedule = list => last(list); @@ -51,12 +46,12 @@ export default function (app) { this.schedules.forEach(i => { let periodIndex = i.semester === 'spring' ? 1 : 0; - let year = i.period.split('-')[periodIndex]; + let year = i.period[periodIndex]; i.text = `${word(i.semester)} ${year} года`; }); - this.currentSchedule = useRoute ? getRoutedSchedule(this.schedules) : getDefaultSchedule(this.schedules); - this.loadSchedule(this.currentSchedule, useRoute); + this.currentSchedule = useRoute ? getSchedule(period, semester) : getDefaultSchedule(this.schedules); + if(this.currentSchedule) this.loadSchedule(this.currentSchedule, useRoute); } }); }; \ No newline at end of file diff --git a/src/client/components/Parallax.js b/src/client/components/Parallax.js index b5d66f26..ca5f5b00 100644 --- a/src/client/components/Parallax.js +++ b/src/client/components/Parallax.js @@ -36,21 +36,14 @@ export default function (app) { return Math.min(max, Math.max(min, value)); }; - return value => fit ? - constrain(map_range(value, r1, r2, d1, d2)) - : - map_range(value, r1, r2, d1, d2) + return value => fit ? constrain(map_range(value, r1, r2, d1, d2)) : map_range(value, r1, r2, d1, d2); } - let hm = mapper([0, 900], [0, 900], true); - let em = mapper([340, 900], [0, 400], true); - //let pm = mapper([0, 1000], [1000, 900], true); + const hm = mapper([0, 900], [0, 900], true); + const em = mapper([340, 900], [0, 400], true); function updateHead(value) { - //console.log(value); - - let v = hm(value); - //let v = value; + const v = hm(value); try { [...document.querySelectorAll('.parallax .parallax__layer')] @@ -59,15 +52,9 @@ export default function (app) { e.style.top = `${layer.y + v * layer.s}px`; }); - let end = document.querySelector('.parallax .parallax__end'); - end.style.height = `${em(value)}px`; - - //let container = document.querySelector('.parallax'); - //container.style.height = '1000px'; - //container.style.height = `${pm(value)}px`; - + const end = document.querySelector('.parallax .parallax__end'); + if (end) end.style.height = `${em(value)}px`; } catch (error) { - console.log(error); } } @@ -77,6 +64,5 @@ export default function (app) { }); } }; - } - ); + }); } diff --git a/src/client/components/ScheduleRecord.js b/src/client/components/ScheduleRecord.js index 4d40ea55..f3c03ff6 100644 --- a/src/client/components/ScheduleRecord.js +++ b/src/client/components/ScheduleRecord.js @@ -1,4 +1,6 @@ import template from '../../templates/components/schedule-record.html'; +import {shortName} from '../../models/collective'; +import {nullSafe} from "../../utils/common"; export default function(app) { app.component('scheduleRecord', { @@ -6,6 +8,9 @@ export default function(app) { record: '<', i: '<' }, - template: template + template: template, + controller: function() { + this.short = nullSafe(shortName); + } }) }; diff --git a/src/client/components/ScheduleSlider.js b/src/client/components/ScheduleSlider.js index 6447cf18..1855b51d 100644 --- a/src/client/components/ScheduleSlider.js +++ b/src/client/components/ScheduleSlider.js @@ -11,13 +11,11 @@ function week(schedule) { current: i === now.getDay() - 1, day: day, date: dates[i], - groups: schedule.map(group => { - return { - name: group.group, - time: group.time, - content: group.week[i] - } - }) + groups: schedule.groups.map(group => ({ + name: group.group, + time: group.time, + content: group.week[i] + })) } }); } @@ -41,7 +39,7 @@ export default function(app) { } if (value) { - let days = week(value); + const days = week(value); scope.days = days; $slick.html( diff --git a/src/client/components/ScheduleTable.js b/src/client/components/ScheduleTable.js index 8c669adf..dd59c340 100644 --- a/src/client/components/ScheduleTable.js +++ b/src/client/components/ScheduleTable.js @@ -1,5 +1,6 @@ import template from '../../templates/components/schedule-table.html'; import {getDates} from '../../utils/date'; +import {shortName} from '../../models/collective'; export default function(app) { app.component('scheduleTable', { @@ -11,8 +12,10 @@ export default function(app) { const now = new Date(); const dates = getDates(now); + this.days = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']; this.isToday = weekDayIndex => (now.getDay() - 1) === weekDayIndex; this.weekDay = weekDayIndex => dates[weekDayIndex]; + this.short = shortName; //var $tbody = element.find('table tbody'); //var $thead = element.find('table thead'); diff --git a/src/client/components/Timeline.js b/src/client/components/Timeline.js index 32622bc5..84a48363 100644 --- a/src/client/components/Timeline.js +++ b/src/client/components/Timeline.js @@ -46,7 +46,7 @@ import {createInstance as createInstagram} from '../../models/TimelinePostInstag const createPost = post => post.type === 'post' ? post : null; const createPosts = items => items .map(createRecord) - .filter(i => i); + .filter(Boolean); function createRecord(post){ for (let fn of [createInstagram, createPost]){ diff --git a/src/config.js b/src/config.js index 5c898695..0b7c4fd4 100644 --- a/src/config.js +++ b/src/config.js @@ -42,6 +42,7 @@ export const redirectionTable = fs.readFileSync(redirectPath, 'utf-8') } }); +export const name = 'Hudozka'; export const host = 'art.shlisselburg.org'; export const protocol = 'https://'; export const homeUrl = protocol + host; diff --git a/src/core/collective.js b/src/core/collective.js index 8956ad1b..e802cc6c 100644 --- a/src/core/collective.js +++ b/src/core/collective.js @@ -1,9 +1,17 @@ import {c} from '../core/db'; -export async function getCollective(query={}, show={}) { - let data = await c('collective').find(query, show).toArray(); - if (!data) { - return null; - } - return data; -} \ No newline at end of file +export async function getCollective(query={}) { + return await c('collective') + .find(query) + .toArray(); +} + +/** + * Find a person by it's id + * + * @param id + * @returns {Promise|*} + */ +export async function getPerson(id){ + return await c('collective').findOne({id: id}); +} diff --git a/src/core/schedule.js b/src/core/schedule.js index ca8dbae8..7a5119aa 100644 --- a/src/core/schedule.js +++ b/src/core/schedule.js @@ -1,64 +1,116 @@ -import co from 'co'; import {c} from '../core/db'; +import {select} from '../utils/fn'; +import {createMap} from '../utils/common'; import {getCollective} from './collective'; -import {populate, scheduleDate} from '../models/schedule'; -import {shortifyName} from '../models/collective'; -import {courses} from '../models/course'; +import {scheduleDate, getPeriod as schedulePeriod} from '../models/schedule'; +import {getCourseName} from '../models/course'; + +const SCHEDULES = 'schedules'; export async function getSchedule(period, semester) { - let data = await c('schedules').findOne({ - period: period, + const schedule = await c(SCHEDULES).findOne({ + $or: [ + {period: period}, + {period: schedulePeriod(period)} + ], semester: semester }); - if (data) return data; - return null; + + return schedule ? migrate(schedule) : null; } -export async function getScheduleList() { - let list = await c('schedules') - .find({}, {period: 1, semester: 1}) +export async function getSchedules(fields = ['_id', 'id', 'period', 'semester']) { + const list = await c(SCHEDULES) + .find({}) .toArray(); - return list - .sort((a, b) => { - let ad = scheduleDate(a); - let bd = scheduleDate(b); - return ad.getTime() - bd.getTime(); - }); + const schedules = await Promise.all(list.map(migrate)); + return schedules + .sort((a, b) => { + const ad = scheduleDate(a).getTime(); + const bd = scheduleDate(b).getTime(); + return ad - bd; + }) + .map(select(fields)); } -export async function populateSchedule(schedule) { - let collective = await getCollective({}, {id: 1, name: 1}); - let collective_dict = collective.reduce((dict, i) => { - dict[i.id] = i.name; - return dict; +async function migrate(schedule) { + const versions = { + '1.0': migrate_10_to_20, + '2.0': migrate_20_to_30, + }; + + const version = schedule.version; + const update = versions[version]; + return version in versions ? migrate(update(schedule)) : schedule; +} + +function migrate_10_to_20(schedule) { + const dayNames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']; + const time = time => time + .map(i => i.split(' ')); + + const week = days => days.reduce((week, lesson, index) => { + if (lesson) { + const day = dayNames[index]; + week[day] = [Object.assign(lesson, { + time: time(lesson.time) + })]; + } + + return week; }, {}); - let populateLesson = populate(courses); - let populateTeacher = populate(collective_dict, i => shortifyName(i)); - - return schedule.map(i => { - return { - group: i.group, - time: i.time, - week: i.week.map(record => { - if (!record) return record; - - let lesson = record.lesson; - let teacher = record.teacher; - let time = record.time; - - return { - time: time, - lesson: populateLesson(lesson), - teacher: { - id: teacher, - url: `/teacher/${teacher}`, - name: populateTeacher(teacher) - } - }; - }) - }; + return Object.assign(schedule, { + version: '2.0', + period: schedulePeriod(schedule.period), + groups: schedule.schedule + .map(group => ({ + name: group.group, + time: group.time, + week: week(group.week) + })) }); -} \ No newline at end of file +} + +async function migrate_20_to_30(schedule) { + const days = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']; + + const persons = await getCollective(); + const person = createMap(i => i.id, persons); + + const week = week => days + .map(day => week[day] || []) + .map(lessons => lessons.map(lesson)); + + const lesson = i => ({ + time: i.time, + course: course(i.lesson), + teacher: teacher(i.teacher) + }); + + const teacher = id => ({ + id: id, + url: `/teacher/${id}`, + name: personName(id) + }); + + const personName = id => person.has(id) ? person.get(id).name : null; + + const course = id => ({ + id: id, + url: null, + title: getCourseName(id) + }); + + return Object.assign(schedule, { + version: '3.0', + groups: schedule.groups + .map(group => ({ + name: group.name, + time: group.time, + week: week(group.week) + })) + }); +} diff --git a/src/index.js b/src/index.js index f7d84ec9..51d7324b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ import 'babel-polyfill'; import config from './config'; +import {name} from './config'; import {connect, collection} from './core/db'; import io from './io'; import server from './server'; @@ -10,11 +11,13 @@ import DataManager from 'hudozka-data'; function main() { async function loop() { - await connect(config.db.uri); + const db = config.db.uri; + await connect(db); try { let data = new DataManager({ - timeline: collection('timeline') + timeline: collection('timeline'), + schedules: collection('schedules') }); let app = server(data); @@ -26,7 +29,9 @@ function main() { return; } - console.log(`Start listening ${config.port}`); + console.log(`App ${name} started`); + console.log(`listening ${config.port}`); + console.log(`db address ${db}`); } loop(); diff --git a/src/models/collective.js b/src/models/collective.js index 54ea159f..e830f7a3 100644 --- a/src/models/collective.js +++ b/src/models/collective.js @@ -1,9 +1,23 @@ -export function shortifyName(name) { - let $ = i => i.replace(/^(.).+/, '$1'); - let [_, first, middle, last] = splitName(name); - return `${$(first)}. ${$(middle)}. ${last}`; +/** + * + * "First Middle Last" -> "F. M. Last" + * + * @param name + * @returns {string} + */ +export function shortName(name) { + const f = i => i.replace(/^(.).+/, '$1'); + const [, first, middle, last] = splitName(name); + return `${f(first)}. ${f(middle)}. ${last}`; } +/** + * + * "First Middle Last" -> ["First", "Middle", "Last"] + * + * @param {string} name + * @returns {Array|{index: number, input: string}} + */ export function splitName(name) { return /(.+)\s(.+)\s(.+)/.exec(name); } diff --git a/src/models/course.js b/src/models/course.js index b2e24bca..a960a9ad 100644 --- a/src/models/course.js +++ b/src/models/course.js @@ -2,24 +2,17 @@ export const courses = { painting: "Живопись", drawing: "Рисунок", sculpting: "Скульптура", + sculpture: "Скульптура", ceramic: "Керамика", + ceramics: "Керамика", composition: "Композиция", history: "История искусства", cg: "Комп. графика" }; -export function getCourseByID(id){ - for(var i=0; i key === id); + return course ? course[1] : null; } - -export function getCourseNameByID(id){ - var p = getCourseByID(id); - if(p) return p.name; - else return null; -} \ No newline at end of file diff --git a/src/models/schedule.js b/src/models/schedule.js index 0e2f919d..c5ee85a6 100644 --- a/src/models/schedule.js +++ b/src/models/schedule.js @@ -1,24 +1,30 @@ -let tryReplace = (dict, record, map = i=>i) => - record in dict ? map(dict[record]) : record; - export const autumnMonth = 8; export const springMonth = 0; - -export const populate = (dict, map) => - id => tryReplace(dict, id, map); - export function scheduleDate(schedule){ let {period, semester} = schedule; let i = semester === 'spring' ? 1 : 0; - let year = parseInt( - period.split('-')[i] - ); + let year = getPeriod(period)[i]; return new Date( year, - i ? springMonth : autumnMonth , + i ? springMonth : autumnMonth, 1 ); } + +/** + * + * f("2012-2015") -> [2012, 2015] + * f(["2012", "2015"]) -> [2012, 2015] + * f([2012, 2015]) -> [2012, 2015] + * + * @param i + * @returns {*} + */ +export function getPeriod(i){ + if (typeof i === 'string') return getPeriod(i.split('-')); + + return i.map(Number); +} diff --git a/src/routes/gallery.js b/src/routes/gallery.js index 714e814b..4d44638b 100644 --- a/src/routes/gallery.js +++ b/src/routes/gallery.js @@ -1,4 +1,3 @@ -import co from 'co'; import compose from 'koa-compose'; import route from 'koa-route'; import {c} from '../core/db'; @@ -9,53 +8,29 @@ export default function () { gallery(), album() ]); - - //app.use(route.get('/gallery/:year', json( - // function *(year) { - // year = parseInt(year); - // let start = new Date(year, 0, 1); - // let end = new Date(year, 11, 31); - // let query = { - // date: {$gte: start, $lt: end} - // }; - // let albums = yield c('albums') - // .find(query) - // .toArray(); - // - // albums = yield albums.map(processAlbum); - // this.body = albums; - // } - //))); }; -function gallery(){ +function gallery() { return route.get('/gallery', json( async(ctx) => { let query = {}; - let fields = ['_id', 'id', 'title', 'date']; - //.reduce((f, i) => { - // f[i] = 1; - // return f; - //}, {}); - let albums = await c('albums') .find(query) .toArray(); - //albums = albums.map(i => { - // return fields.reduce((a, key)=> { - // a[key] = i[key]; - // return a; - // }, {}); - //}); - - albums = await Promise.all(albums.map(processAlbum)); - ctx.body = albums; + ctx.body = await Promise.all(albums + .sort((a, b) => { + const da = a.date.getTime(); + const db = b.date.getTime(); + return db - da; + }) + .map(processAlbum) + ); } )); } -function album(){ +function album() { return route.get('/album/:id', json( async(ctx, id) => { let record = await c('albums').findOne({ @@ -67,25 +42,14 @@ function album(){ )); } -let processAlbum = album => co(function *() { - //album.url = `/gallery/{year}{course}{album}` - // .replace('{year}', album.date.getFullYear()) - // .replace('{course}', album.course_uri) - // .replace('{album}', album.uri); +const processAlbum = async album => { album.url = `/album/${album.id}`; - //album.content = yield album.content.map(product_id => co(function *() { - // let product = yield c('products').findOne({_id: product_id}); - // if (product) return product; - // else return null; - //})); - - //let teacher = yield c('collective').findOne({id: album.teacher}); - //album.teacher = teacher.name; - - let previewImageId = album.images[0]; - let image = yield c('images').findOne({_id: previewImageId}); - album.preview = image.data.medium; + const previewImageId = album.images.length ? album.images[0] : null; + if (previewImageId) { + const image = await c('images').findOne({_id: previewImageId}); + album.preview = image.data.medium; + } return album; -}); \ No newline at end of file +}; diff --git a/src/routes/news.js b/src/routes/news.js index 907a8f3c..ca55852e 100644 --- a/src/routes/news.js +++ b/src/routes/news.js @@ -23,35 +23,33 @@ function news() { 'text/html': redirect, 'text/plain': redirect, 'application/json': async ctx => { - let limit = parseInt(ctx.query['limit']) || 10; - let skip = parseInt(ctx.query['skip']) || 0; + const limit = parseInt(ctx.query['limit']) || 20; + const skip = parseInt(ctx.query['skip']) || 0; - let query = {}; + const query = {}; if (ctx.query.type) query.type = ctx.query.type; - let pinned = []; - if(skip == 0) { - const now = new Date(); - pinned = await c('timeline') - .find({until: {$gte: now}}) - .toArray() - .then(i => i.sort(sortNewsByDate)); - } + const now = new Date(); + const pinned = skip != 0 ? [] : await c('timeline') + .find({until: {$gte: now}}) + .toArray(); - let sid = id => id + ''; - let pinnedIds = pinned.map(p => sid(p['_id'])); + const id = i => i._id; + const pinnedIds = pinned.map(id); let posts = await c('timeline') .find(query) .sort({date: -1}) .skip(skip) .limit(limit) - .toArray() - .then(items => items.filter( - i => pinnedIds.indexOf(sid(i['_id'])) < 0 - )); + .toArray(); - ctx.body = pinned.concat(posts); + ctx.body = [ + ...pinned.sort(sortNewsByDate), + ...posts.filter( + i => !pinnedIds.includes(id(i)) + ) + ]; } })); } diff --git a/src/routes/schedule.js b/src/routes/schedule.js index 25ff01fa..8a283d80 100644 --- a/src/routes/schedule.js +++ b/src/routes/schedule.js @@ -1,9 +1,8 @@ import compose from 'koa-compose'; -import co from 'co'; import route from 'koa-route'; import {json} from './'; -import {getSchedule, getScheduleList, populateSchedule} from '../core/schedule'; +import {getSchedule, getSchedules} from '../core/schedule'; export default function () { return compose([ @@ -16,11 +15,12 @@ function list() { return route.get('/schedule/list', json( async(ctx) => { ctx.type = 'application/json'; - let data = await getScheduleList(); + const data = await getSchedules(); + if (!data) ctx.status = 404; else ctx.body = data; } - )) + )); } function schedule() { @@ -34,11 +34,6 @@ function schedule() { return; } - let doPopulate = ctx.query.populate || false; - if (doPopulate) { - data.schedule = await populateSchedule(data.schedule); - } - ctx.body = data; } )) diff --git a/src/server.js b/src/server.js index b0f8a628..09066327 100644 --- a/src/server.js +++ b/src/server.js @@ -6,7 +6,7 @@ import logger from 'koa-logger'; import convert from 'koa-convert'; import conditional from 'koa-conditional-get'; import etag from 'koa-etag'; -import prerender from 'koa-prerender'; +// import prerender from 'koa-prerender'; import helmet from 'koa-helmet'; import mount from 'koa-mount'; import bodyParser from 'koa-bodyparser'; @@ -36,8 +36,8 @@ export default function(store){ app.use(apis(store)); app.use($(conditional())); app.use($(etag())); - app.use(prerenderRmFragment()); - app.use($(prerender(config.prerender))); + // app.use(prerenderRmFragment()); + // app.use($(prerender(config.prerender))); app.use(serve(dirPublic)); app.use(serve(dirTemplates)); app.use($(redirect(redirectionTable))); diff --git a/src/style/components/schedule-table.scss b/src/style/components/schedule-table.scss index 83732bf7..f99dbd20 100644 --- a/src/style/components/schedule-table.scss +++ b/src/style/components/schedule-table.scss @@ -2,7 +2,6 @@ $current-week-day-color: $blue-color; - $cell-width: 9em; $cell-tablet-width: 7.5em; @@ -36,7 +35,6 @@ $current-day-column-border-thickness: 3px; &__head{ display: flex; - justify-content: space-between; .date { margin: .5em 0 0; @@ -89,17 +87,11 @@ $current-day-column-border-thickness: 3px; text-align: center; } -} -//.canceled:before { -// position: absolute; -// content: ""; -// width: 70%; -// height: 100%; -// top: 0; -// left: 15%; -// background-image: url(lesson-canceled.svg); -//} + &__course{ + margin-bottom: $half-margin; + } +} @media screen and (min-width: $tablet-min-width) and (max-width: $tablet-max-width) { .schedule-table { @@ -125,10 +117,6 @@ $current-day-column-border-thickness: 3px; } } - //&__body-row{ - // justify-content: center; - //} - &__body-group{ min-width: $group-cell-tablet-width; @@ -141,7 +129,4 @@ $current-day-column-border-thickness: 3px; min-width: $cell-tablet-width; } } - - //@media screen and (orientation: portrait) { - //} } diff --git a/src/style/content.scss b/src/style/content.scss index 72813564..da765cad 100644 --- a/src/style/content.scss +++ b/src/style/content.scss @@ -21,10 +21,12 @@ &.content--padding-top--double{ padding-top: $double-margin; + padding-bottom: $single-margin; } &.content--padding-top--menu{ padding-top: $menu-thickness + $double-margin; + padding-bottom: $single-margin; } } diff --git a/src/style/kazimir/video.scss b/src/style/kazimir/video.scss new file mode 100644 index 00000000..205085d9 --- /dev/null +++ b/src/style/kazimir/video.scss @@ -0,0 +1,16 @@ +@import "../variables"; + +.kazimir-video { + width: 100%; + height: 0; + position: relative; + padding-bottom: $embed-video-ratio; + + margin-bottom: $single-margin; + + iframe { + width: 100%; + height: 100%; + position: absolute; + } +} diff --git a/src/style/main-image.scss b/src/style/main-image.scss index 3b32a6ed..eb6af9b4 100644 --- a/src/style/main-image.scss +++ b/src/style/main-image.scss @@ -15,4 +15,10 @@ min-width: 1280px; width: 1280px; } -} \ No newline at end of file +} + +@media (max-width: $mobile-width) { + .main-image { + display: none; + } +} diff --git a/src/style/style.scss b/src/style/style.scss index e66c0606..91c6b993 100644 --- a/src/style/style.scss +++ b/src/style/style.scss @@ -33,3 +33,5 @@ @import "tape-viewer"; @import "footer"; + +@import "kazimir/video"; diff --git a/src/style/variables.scss b/src/style/variables.scss index 043d5fc2..f4e7937c 100644 --- a/src/style/variables.scss +++ b/src/style/variables.scss @@ -10,6 +10,8 @@ $content-wide: 80%; $content-semi-wide: 70%; $content-thin: 60%; +$embed-video-ratio: 56.25%; + //MARGINS $small-margin: .2em; $half-margin: .5em; diff --git a/src/templates/components/page-album.html b/src/templates/components/page-album.html index f8849c9b..5313e3c6 100644 --- a/src/templates/components/page-album.html +++ b/src/templates/components/page-album.html @@ -4,6 +4,8 @@
+
+ diff --git a/src/templates/components/page-article.html b/src/templates/components/page-article.html index 8d6a32e7..c4b65755 100644 --- a/src/templates/components/page-article.html +++ b/src/templates/components/page-article.html @@ -1,4 +1,6 @@
+ +
diff --git a/src/templates/components/schedule-record.html b/src/templates/components/schedule-record.html index 674c0878..4d463363 100644 --- a/src/templates/components/schedule-record.html +++ b/src/templates/components/schedule-record.html @@ -1,8 +1,8 @@ -
+
Группа {{$ctrl.record.name}} ({{$ctrl.record.time}})
-

{{$ctrl.record.content.lesson}}

-

{{$ctrl.record.content.teacher.name}}

-

{{time}}

+

{{$ctrl.record.content[0].course.title}}

+

{{$ctrl.short($ctrl.record.content[0].teacher.name)}}

+

{{time.join('—')}}

\ No newline at end of file diff --git a/src/templates/components/schedule-table.html b/src/templates/components/schedule-table.html index 65b4c43a..c4c9f478 100644 --- a/src/templates/components/schedule-table.html +++ b/src/templates/components/schedule-table.html @@ -29,10 +29,10 @@
+ ng-repeat="i in $ctrl.schedule.groups">
-

{{i.group}}

+

{{i.name}}

{{i.time}}

@@ -40,9 +40,13 @@ ng-repeat="day in i.week track by $index" ng-class="{current: $ctrl.isToday($index)}" > -

{{day.lesson}}

-

{{day.teacher.name}}

-

{{time}}

+
diff --git a/src/templates/main.html b/src/templates/main.html index 590058f2..ff8cf9d6 100644 --- a/src/templates/main.html +++ b/src/templates/main.html @@ -12,6 +12,12 @@ Шлиссельбургская Детская Художественная Школа + + + + + + @@ -76,5 +82,15 @@ + + diff --git a/src/utils/common.js b/src/utils/common.js index f445eee0..ee15c886 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -79,3 +79,26 @@ export function assign(dict, key, value){ export function map(v, fn) { return value => fn(value) ? v : value; } + +/** + * + * f(g, [x, y, z, ...]) => map of {g(x): x, g(y): y, g(z): z} + * + * @param list + * @param selector + * @returns {*} + */ +export function createMap(selector, list){ + return list.reduce((map, i) => map.set(selector(i), i), new Map()); +} + +/** + * + * f(g) => x ? g(x) : null + * + * @param {function} fn + * @returns {function(*=): null} + */ +export function nullSafe(fn){ + return i => i ? fn(i) : null; +} diff --git a/src/utils/fn.js b/src/utils/fn.js new file mode 100644 index 00000000..caaf894a --- /dev/null +++ b/src/utils/fn.js @@ -0,0 +1,13 @@ +/** + * + * f([x, y, z, ...]) -> f(v) -> {v.x, v.y, v.z} + * + * @param fields + * @returns {function(*): *} + */ +export function select(fields=[]){ + return i => fields.reduce((out, key) => { + out[key] = i[key]; + return out; + }, {}); +} diff --git a/test/models/schedule.js b/test/models/schedule.js new file mode 100644 index 00000000..581d6520 --- /dev/null +++ b/test/models/schedule.js @@ -0,0 +1,17 @@ +import {getPeriod} from '../../src/models/schedule'; + +describe('models', () => { + describe('schedule.js', () => { + describe('getPeriod', () => { + it('should accept a string and return an array', () => { + getPeriod('2016-2017').should.be.Array; + }); + + it('should accept a string "XXXX-YYYY" and return an array [XXXX, YYYY]', () => { + const sample = '2016-2017'; + const result = getPeriod(sample); + result.should.be.eql([2016, 2017]); + }); + }); + }); +}); \ No newline at end of file