update
This commit is contained in:
1 parent
be398bf5ce
commit
f121eff445
38 files changed
+480
-249
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
4.4.7
|
||||
6.8.0
|
||||
@@ -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
|
||||
$ <ENV> 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=<ip_address>
|
||||
USER=<mongo_user>
|
||||
PWD=<mongo_password>
|
||||
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))
|
||||
+1
-1
@@ -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'
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -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"
|
||||
}
|
||||
}
|
||||
+16
-1
@@ -211,8 +211,23 @@ app.config(($locationProvider, $routeProvider) => {
|
||||
|
||||
app.run(($location, $rootScope, $http) => {
|
||||
$rootScope.$on('$routeChangeSuccess', (event, current) => {
|
||||
let title = current['$$route'].title;
|
||||
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) => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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) {
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -11,13 +11,11 @@ function week(schedule) {
|
||||
current: i === now.getDay() - 1,
|
||||
day: day,
|
||||
date: dates[i],
|
||||
groups: schedule.map(group => {
|
||||
return {
|
||||
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(
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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]){
|
||||
|
||||
@@ -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;
|
||||
|
||||
+13
-5
@@ -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;
|
||||
export async function getCollective(query={}) {
|
||||
return await c('collective')
|
||||
.find(query)
|
||||
.toArray();
|
||||
}
|
||||
return data;
|
||||
|
||||
/**
|
||||
* Find a person by it's id
|
||||
*
|
||||
* @param id
|
||||
* @returns {Promise|*}
|
||||
*/
|
||||
export async function getPerson(id){
|
||||
return await c('collective').findOne({id: id});
|
||||
}
|
||||
+97
-45
@@ -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 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)
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return schedule.map(i => {
|
||||
return {
|
||||
group: i.group,
|
||||
const week = week => days
|
||||
.map(day => week[day] || [])
|
||||
.map(lessons => lessons.map(lesson));
|
||||
|
||||
const lesson = i => ({
|
||||
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)
|
||||
}
|
||||
};
|
||||
})
|
||||
};
|
||||
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)
|
||||
}))
|
||||
});
|
||||
}
|
||||
+8
-3
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+7
-14
@@ -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<courses.length; i++){
|
||||
var p = courses[i];
|
||||
if(p.id == id){
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getCourseNameByID(id){
|
||||
var p = getCourseByID(id);
|
||||
if(p) return p.name;
|
||||
else return null;
|
||||
export function getCourseName(id) {
|
||||
const course = Object
|
||||
.entries(courses)
|
||||
.find(([key]) => key === id);
|
||||
return course ? course[1] : null;
|
||||
}
|
||||
+16
-10
@@ -1,20 +1,11 @@
|
||||
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,
|
||||
@@ -22,3 +13,18 @@ export function scheduleDate(schedule){
|
||||
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);
|
||||
}
|
||||
+14
-50
@@ -1,4 +1,3 @@
|
||||
import co from 'co';
|
||||
import compose from 'koa-compose';
|
||||
import route from 'koa-route';
|
||||
import {c} from '../core/db';
|
||||
@@ -9,48 +8,24 @@ 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() {
|
||||
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)
|
||||
);
|
||||
}
|
||||
));
|
||||
}
|
||||
@@ -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});
|
||||
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;
|
||||
});
|
||||
};
|
||||
+14
-16
@@ -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')
|
||||
const pinned = skip != 0 ? [] : await c('timeline')
|
||||
.find({until: {$gte: now}})
|
||||
.toArray()
|
||||
.then(i => i.sort(sortNewsByDate));
|
||||
}
|
||||
.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))
|
||||
)
|
||||
];
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
))
|
||||
|
||||
+3
-3
@@ -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)));
|
||||
|
||||
@@ -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) {
|
||||
//}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -16,3 +16,9 @@
|
||||
width: 1280px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
.main-image {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -33,3 +33,5 @@
|
||||
|
||||
@import "tape-viewer";
|
||||
@import "footer";
|
||||
|
||||
@import "kazimir/video";
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<div ng-bind-html="$.post"></div>
|
||||
</album>
|
||||
|
||||
<div id="disqus_thread"></div>
|
||||
|
||||
<!--<breadcrumbs crumbs="$.crumbs"></breadcrumbs>-->
|
||||
|
||||
<!--<div class="album-block">-->
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<section class="content content_thin content--padding-top--menu">
|
||||
<!--<h-article ng-repeat="i in $.posts" post="i"></h-article>-->
|
||||
<h-article post="$.article"></h-article>
|
||||
|
||||
<div id="disqus_thread"></div>
|
||||
</section>
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="schedule-record" ng-class="{even: $ctrl.i % 2 === 0, empty: !$ctrl.record.content.lesson}">
|
||||
<div class="schedule-record" ng-class="{even: $ctrl.i % 2 === 0, empty: !$ctrl.record.content[0].lesson}">
|
||||
<div class="schedule-record__head">Группа {{$ctrl.record.name}} ({{$ctrl.record.time}})</div>
|
||||
<div class="schedule-record__main">
|
||||
<p class="schedule-record__name">{{$ctrl.record.content.lesson}}</p>
|
||||
<p class="schedule-record__tutor"><a href="{{$ctrl.record.content.teacher.url}}">{{$ctrl.record.content.teacher.name}}</a></p>
|
||||
<p class="schedule-record__time" ng-repeat="time in $ctrl.record.content.time">{{time}}</p>
|
||||
<p class="schedule-record__name">{{$ctrl.record.content[0].course.title}}</p>
|
||||
<p class="schedule-record__tutor"><a href="{{$ctrl.record.content[0].teacher.url}}">{{$ctrl.short($ctrl.record.content[0].teacher.name)}}</a></p>
|
||||
<p class="schedule-record__time" ng-repeat="time in $ctrl.record.content[0].time">{{time.join('—')}}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,10 +29,10 @@
|
||||
|
||||
<div class="schedule-table__body">
|
||||
<div class="schedule-table__body-row"
|
||||
ng-repeat="i in $ctrl.schedule">
|
||||
ng-repeat="i in $ctrl.schedule.groups">
|
||||
|
||||
<div class="schedule-table__body-group">
|
||||
<p class="name">{{i.group}}</p>
|
||||
<p class="name">{{i.name}}</p>
|
||||
<p class="time">{{i.time}}</p>
|
||||
</div>
|
||||
|
||||
@@ -40,9 +40,13 @@
|
||||
ng-repeat="day in i.week track by $index"
|
||||
ng-class="{current: $ctrl.isToday($index)}"
|
||||
>
|
||||
<p class="lesson"><a href="">{{day.lesson}}</a></p>
|
||||
<p class="teacher"><a href="{{day.teacher.url}}">{{day.teacher.name}}</a></p>
|
||||
<p class="time" ng-repeat="time in day.time">{{time}}</p>
|
||||
<div class="schedule-table__course"
|
||||
ng-repeat="lesson in day track by $index"
|
||||
>
|
||||
<p class="lesson"><a href="">{{lesson.course.title}}</a></p>
|
||||
<p class="teacher"><a href="{{lesson.teacher.url}}">{{$ctrl.short(lesson.teacher.name)}}</a></p>
|
||||
<p class="time" ng-repeat="time in lesson.time">{{time.join(' — ')}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
<base href="/">
|
||||
<title ng-bind="title">Шлиссельбургская Детская Художественная Школа</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="//static.shlisselburg.org/art/graphics/favicon-32.png" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="//static.shlisselburg.org/art/graphics/favicon-57.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="//static.shlisselburg.org/art/graphics/favicon-72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="//static.shlisselburg.org/art/graphics/favicon-114.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="//static.shlisselburg.org/art/graphics/favicon-144.png" />
|
||||
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<!--<link rel="stylesheet" href="/animate.min.css">-->
|
||||
<link rel="stylesheet" href="/fonts/PTSans/stylesheet.css">
|
||||
@@ -76,5 +82,15 @@
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var d = document;
|
||||
var s = d.createElement('script');
|
||||
s.src = '//hudozka.disqus.com/embed.js';
|
||||
s.setAttribute('data-timestamp', Date.now());
|
||||
(d.head || d.body).appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}, {});
|
||||
}
|
||||
@@ -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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user