This commit is contained in:
tmshv committed 2016-11-11 02:36:03 +03:00
1 parent be398bf5ce
commit f121eff445
38 files changed
+484 -253

No files matched your search

+1 -1
View File
@@ -1 +1 @@
4.4.7
6.8.0
+59 -10
View File
@@ -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
View File
@@ -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'
+2
View File
@@ -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);
}
}
+48
View File
@@ -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
View File
@@ -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
View File
@@ -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) => {
+14 -19
View File
@@ -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);
}
});
};
+7 -21
View File
@@ -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) {
});
}
};
}
);
});
}
+6 -1
View File
@@ -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);
}
})
};
+3 -5
View File
@@ -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(
+3
View File
@@ -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');
+1 -1
View File
@@ -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]){
+1
View File
@@ -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;
+14 -6
View File
@@ -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;
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});
}
+97 -45
View File
@@ -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)
}))
});
}
return schedule.map(i => {
return {
group: i.group,
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,
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
View File
@@ -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();
+18 -4
View File
@@ -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
View File
@@ -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;
}
+17 -11
View File
@@ -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);
}
+16 -52
View File
@@ -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});
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
View File
@@ -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))
)
];
}
}));
}
+4 -9
View File
@@ -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
View File
@@ -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)));
+4 -19
View File
@@ -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) {
//}
}
+2
View File
@@ -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;
}
}
+16
View File
@@ -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;
}
}
+6
View File
@@ -16,3 +16,9 @@
width: 1280px;
}
}
@media (max-width: $mobile-width) {
.main-image {
display: none;
}
}
+2
View File
@@ -33,3 +33,5 @@
@import "tape-viewer";
@import "footer";
@import "kazimir/video";
+2
View File
@@ -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;
+2
View File
@@ -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>
+9 -5
View File
@@ -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>
+16
View File
@@ -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>
+23
View File
@@ -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;
}
+13
View File
@@ -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;
}, {});
}
+17
View File
@@ -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]);
});
});
});
});