diff --git a/package.json b/package.json index c2ac254b..95c268bc 100644 --- a/package.json +++ b/package.json @@ -21,14 +21,16 @@ "angular": "^1.5.3", "angular-route": "^1.5.3", "angular-spinner": "^0.8.1", - "angulartics": "^1.2.1", - "angulartics-google-analytics": "^0.1.4", + "angulartics": "^1.0.0", + "angulartics-google-analytics": "^0.3.0", + "async-file": "^2.0.2", "babel-core": "^6.7.4", "babel-polyfill": "^6.7.4", "babel-preset-es2015": "^6.5.0", "babel-preset-stage-0": "^6.5.0", "co": "^4.4.0", "ejson": "^2.1.2", + "handlebars": "^4.0.5", "hudozka-api-v1": "file:modules/hudozka-api-v1", "hudozka-data": "file:modules/hudozka-data", "hudozka-middlewares": "file:modules/hudozka-middlewares", diff --git a/src/client/api/API.js b/src/client/api/API.js index d6f79aae..0f8fdee8 100644 --- a/src/client/api/API.js +++ b/src/client/api/API.js @@ -5,6 +5,7 @@ import APITeacher from './APITeacher'; import APISchedule from './APISchedule'; import APIEvent from './APIEvent'; import APIArticle from './APIArticle'; +import APIPage from './APIPage'; export default class API { constructor(request) { @@ -15,5 +16,6 @@ export default class API { this.schedule = new APISchedule(request); this.event = new APIEvent(request); this.article = new APIArticle(request); + this.page = new APIPage(request); } } diff --git a/src/client/api/APIPage.js b/src/client/api/APIPage.js new file mode 100644 index 00000000..200e54aa --- /dev/null +++ b/src/client/api/APIPage.js @@ -0,0 +1,7 @@ +import EndPoint from './EndPoint'; + +export default class APIPage extends EndPoint { + url(url) { + return this.get(url); + } +} diff --git a/src/client/app.js b/src/client/app.js index 61a291c9..6412f4c8 100644 --- a/src/client/app.js +++ b/src/client/app.js @@ -1,3 +1,4 @@ +import {getInitialData} from './core/init'; import Navigation from './components/Navigation'; import MainMenu from './components/MainMenu'; import Breadcrumbs from './components/Breadcrumbs'; @@ -20,6 +21,7 @@ import ArticlePageControl from './components/ArticlePageControl'; import Album from './components/Album'; import Parallax from './components/Parallax'; +import Page from './components/CloudPage'; import PageSchool from './components/PageSchool'; import PageEvents from './components/PageEvents'; import PageArticle from './components/PageArticle'; @@ -61,6 +63,21 @@ app.config(($locationProvider, $routeProvider) => { $locationProvider.hashPrefix('!'); $locationProvider.html5Mode(true); + const pages = getInitialData().pages + .map(i => i.url) + .map(i => ({ + name: i, + template: '', + resolveAs: '$', + resolve: { + data: (api) => { + return api.page + .url(location.pathname) + .then(i => i.data) + } + } + })); + let routers = [ { name: '/', @@ -191,6 +208,7 @@ app.config(($locationProvider, $routeProvider) => { } } ] + .concat(pages) .map(i => { let path = i.name; delete i.name; @@ -264,6 +282,7 @@ app.run(($location, $rootScope, $http) => { GalleryItem, AlbumCollection, TeacherProfile, + Page, PageSchool, PageArticle, PageEvents, diff --git a/src/client/components/CloudPage.js b/src/client/components/CloudPage.js new file mode 100644 index 00000000..1b434b80 --- /dev/null +++ b/src/client/components/CloudPage.js @@ -0,0 +1,15 @@ +import template from '../../templates/components/cloud-page.html'; + +export default function (app) { + app.component('cloudPage', { + bindings: { + page: ' route.get('/', index()); -export function routes(){ - return compose([ - main(), - sitemap(), - schedule(), - news(), - gallery(), - documents(), - teachers(), - events(), - articles(), - error404() - ]); +export function routes() { + return compose([ + main(), + sitemap(), + schedule(), + news(), + gallery(), + documents(), + teachers(), + events(), + articles(), + pages(), + error404() + ]); } export function queryObject() { - let toTrue = map(true, i => i === 'true'); - let toFalse = map(false, i => i === 'false'); - const processors = [toTrue, toFalse]; + let toTrue = map(true, i => i === 'true'); + let toFalse = map(false, i => i === 'false'); + const processors = [toTrue, toFalse]; - return async (ctx, next) => { - let query = ctx.query; - ctx.query = Object.keys(query) - .reduce((query, key) => assign( - query, - key, - processors - .reduce((value, fn) => fn(value), query[key]) - ), query); + return async(ctx, next) => { + let query = ctx.query; + ctx.query = Object + .keys(query) + .reduce((query, key) => assign( + query, + key, + processors + .reduce((value, fn) => fn(value), query[key]) + ), query); - await next(); - } + await next(); + } } export function accepts(routes) { - return async function (ctx) { - let request = ctx.request; - let types = Object.keys(routes) - .filter(type => request.accepts(type)); + return async function (ctx) { + let request = ctx.request; + let types = Object.keys(routes) + .filter(type => request.accepts(type)); - if (types.length) { - let fn = routes[types[0]]; + if (types.length) { + let fn = routes[types[0]]; - await fn.apply(ctx, arguments); - } else { - ctx.status = 406; - } - }; + await fn.apply(ctx, arguments); + } else { + ctx.status = 406; + } + }; } export function index(fn) { - let filename = config['defaultIndex']; + return async function (ctx) { + let test = true; + if (fn) test = await fn.apply(ctx, arguments); + if (!test) ctx.status = 404; - return async function (ctx){ - let test = true; - if (fn) test = await fn.apply(ctx, arguments); + const data = await getInitialData(); + const source = await readFile(indexFile, 'utf-8'); + const template = handlebars.compile(source); - if (!test) ctx.status = 404; - - ctx.type = 'text/html'; - ctx.body = fs.createReadStream(filename); - } + ctx.type = 'text/html'; + ctx.body = template({data: JSON.stringify(data)}); + } } -export function json(fn){ - return accepts({ - 'text/html': index(), - 'text/plain': index(), - 'application/json': fn - }); +export function json(fn) { + return accepts({ + 'text/html': index(), + 'text/plain': index(), + 'application/json': fn + }); +} + +async function getInitialData() { + const pages = await getPageUrls(); + return {pages: pages}; } diff --git a/src/routes/pages.js b/src/routes/pages.js new file mode 100644 index 00000000..f6cec2fc --- /dev/null +++ b/src/routes/pages.js @@ -0,0 +1,24 @@ +import route from 'koa-route'; +import {c} from '../core/db'; +import {accepts, index} from './'; + +async function exists(ctx) { + const page = await getPage(ctx.path); + return Boolean(page); +} + +async function getPage(path) { + return await c('pages').findOne({url: path}); +} + +export default function () { + return route.get('*', accepts({ + 'text/html': index(exists), + 'text/plain': index(exists), + 'application/json': async(ctx) => { + const page = await getPage(ctx.path); + if (!page) ctx.status = 404; + else ctx.body = page; + } + })) +}; diff --git a/src/server.js b/src/server.js index 09066327..568b4661 100644 --- a/src/server.js +++ b/src/server.js @@ -6,61 +6,63 @@ 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'; -import api_v1 from 'hudozka-api-v1'; -import config from './config'; -import {redirectionTable} from './config'; +import apiV1 from 'hudozka-api-v1'; +import config, {redirectionTable} from './config'; import {routes, queryObject} from './routes'; import {redirect} from './routes/redirect'; import {services as serviceKeys, authChecker} from './core/service'; +import handlebars from 'handlebars'; const dirPublic = path.join(__dirname, '../public'); const dirTemplates = path.join(__dirname, 'templates'); -export default function(store){ - const $ = convert; - - const app = new Koa(); - app.proxy = true; +handlebars.registerHelper('raw-helper', options => options.fn()); - app.use(bodyParser({ - extendTypes: { - json: 'application/ejson' - } - })); - app.use(logger()); - app.use(apis(store)); - app.use($(conditional())); - app.use($(etag())); - // app.use(prerenderRmFragment()); - // app.use($(prerender(config.prerender))); - app.use(serve(dirPublic)); - app.use(serve(dirTemplates)); - app.use($(redirect(redirectionTable))); - app.use($(helmet())); - app.use(queryObject()); - app.use(routes()); +export default function (store) { + const $ = convert; - return Server(app.callback()); + const app = new Koa(); + app.proxy = true; + + app.use(bodyParser({ + extendTypes: { + json: 'application/ejson' + } + })); + app.use(logger()); + app.use(apis(store)); + app.use($(conditional())); + app.use($(etag())); + app.use(prerenderRmFragment()); + app.use($(prerender(config.prerender))); + app.use(serve(dirPublic)); + app.use(serve(dirTemplates)); + app.use($(redirect(redirectionTable))); + app.use($(helmet())); + app.use(queryObject()); + app.use(routes()); + + return Server(app.callback()); } -function apis(store){ - let checkAuth = authChecker(serviceKeys); - return mount('/api/v1', api_v1(checkAuth, store)); +function apis(store) { + let checkAuth = authChecker(serviceKeys); + return mount('/api/v1', apiV1(checkAuth, store)); } -function prerenderRmFragment(){ - return async (ctx, next) => { - await next(); +function prerenderRmFragment() { + return async(ctx, next) => { + await next(); - try { - let xp = ctx.response.header['x-prerender'] == 'true'; - if (xp) ctx.body = ctx.body.replace('', ''); - } catch (e) { - } - } + try { + let xp = ctx.response.header['x-prerender'] === 'true'; + if (xp) ctx.body = ctx.body.replace('', ''); + } catch (e) { + } + } } diff --git a/src/style/body-wrapper.scss b/src/style/body-wrapper.scss index 9e1bcacf..a61f0073 100644 --- a/src/style/body-wrapper.scss +++ b/src/style/body-wrapper.scss @@ -2,6 +2,9 @@ .body-wrapper { background-color: white; + + font-family: $text-font; + font-size: $default-font-size; } .body-wrapper__content { diff --git a/src/style/components/album.scss b/src/style/components/album.scss index fc4313c9..6a1458c0 100644 --- a/src/style/components/album.scss +++ b/src/style/components/album.scss @@ -7,7 +7,7 @@ margin-bottom: 1.25em; h3 { - font-size: $big-font-size; + font-size: $biggest-font-size; text-align: center; } diff --git a/src/style/components/article.scss b/src/style/components/article.scss index 90593fb9..a64a730b 100644 --- a/src/style/components/article.scss +++ b/src/style/components/article.scss @@ -7,7 +7,7 @@ margin-bottom: 1.25em; h3 { - font-size: $big-font-size; + font-size: $biggest-font-size; text-align: center; } @@ -33,13 +33,16 @@ margin: 1.25em auto $small-margin; } - ul{ - margin-bottom: 1.25em; - - list-style: none; + ul, ol{ + margin-left: $half-margin; + margin-bottom: $single-margin; padding-left: $single-margin; } + ul{ + list-style: disc; + } + p{ margin-bottom: 1.25em; } diff --git a/src/style/components/cloud-page.scss b/src/style/components/cloud-page.scss new file mode 100644 index 00000000..e3b49649 --- /dev/null +++ b/src/style/components/cloud-page.scss @@ -0,0 +1,79 @@ +@import "../variables"; + +.cloud-page { + margin-bottom: $post-margin; + + &__body{ + + a { + color: $dark-color; + text-decoration: none; + border-bottom: 1px solid lighten($dark-color, 10%); + + &:hover { + border-bottom: 1px solid lighten($dark-color, 25%); + } + + &:active { + border-bottom: 1px solid $dark-color; + } + } + + h1{ + text-align: center; + margin-bottom: $single-margin; + + font-size: $biggest-font-size; + } + + h2 { + text-align: left; + margin-top: $double-margin; + font-family: $text-font; + } + + ul, ol{ + margin-left: $half-margin; + margin-bottom: $single-margin; + padding-left: $single-margin; + } + + ul{ + list-style: disc; + } + + p{ + margin-bottom: 1.25em; + } + + blockquote { + background-color: hsl(215, 10%, 96%); + padding: 1em 1em .5em; + margin: $single-margin 0; + font-family: $title-font; + font-style: italic; + font-weight: normal; + font-size: 1.25em; + + cite { + margin-top: $single-margin; + display: block; + color: #aaa; + font-size: initial; + font-family: $text-font; + text-align: right; + margin-right: $single-margin; + } + } + } +} + +@media (max-width: $mobile-width) { + .cloud-page { + &__body { + img { + max-height: 20em; + } + } + } +} diff --git a/src/style/components/main-menu.scss b/src/style/components/main-menu.scss index b6ebcdee..87b71925 100644 --- a/src/style/components/main-menu.scss +++ b/src/style/components/main-menu.scss @@ -3,7 +3,7 @@ .main-menu { white-space: nowrap; - font-size: 1.1em; + font-size: $normal-font-size; color: $menu-inactive-item-color; list-style: none; diff --git a/src/style/components/timeline-record.scss b/src/style/components/timeline-record.scss index 42ad514a..6d64a020 100644 --- a/src/style/components/timeline-record.scss +++ b/src/style/components/timeline-record.scss @@ -4,8 +4,6 @@ margin: 0 0 $post-margin; .date { - //margin: 0 0 0.45em; - //font-size: 0.75em; color: $light-color; } } diff --git a/src/style/components/timeline.scss b/src/style/components/timeline.scss index c3b60b5a..f3012069 100644 --- a/src/style/components/timeline.scss +++ b/src/style/components/timeline.scss @@ -10,7 +10,7 @@ display: flex; justify-content: center; - button.more{ + button.more { position: relative; top: -2px; @@ -20,13 +20,13 @@ padding: 0 10px; background: none; - font-size: 14pt; + font-size: $normal-font-size; - &:hover{ + &:hover { background: gainsboro; } - &:active{ + &:active { top: 0; } } diff --git a/src/style/footer.scss b/src/style/footer.scss index cfcbca51..cf234f8e 100644 --- a/src/style/footer.scss +++ b/src/style/footer.scss @@ -9,6 +9,8 @@ footer { position: relative; background-color: $darker-color; + font-size: $smaller-font-size; + &:before { $s: 3px; diff --git a/src/style/hudozka-title.scss b/src/style/hudozka-title.scss index 365f7d27..4cf365bb 100644 --- a/src/style/hudozka-title.scss +++ b/src/style/hudozka-title.scss @@ -8,20 +8,19 @@ p { color: $light-color; margin-bottom: $single-margin; + font-size: $smaller-font-size; } h1 { margin-top: $single-margin; - font-size: 2em; + font-size: $biggest-font-size; } } @media (max-width: $mobile-width) { .hudozka-title { - text-align: center; - h1 { - font-size: 1.5em; + font-size: $big-font-size; } } } diff --git a/src/style/main.scss b/src/style/main.scss index e6f6479a..e5bc6ef4 100644 --- a/src/style/main.scss +++ b/src/style/main.scss @@ -10,7 +10,6 @@ html, body { } body { - font-family: $text-font; min-width: 320px; } diff --git a/src/style/style.scss b/src/style/style.scss index 91c6b993..9ded95b8 100644 --- a/src/style/style.scss +++ b/src/style/style.scss @@ -11,6 +11,7 @@ @import "components/timeline-record-instagram"; @import "components/schedule-table"; @import "components/footer-links"; +@import "components/cloud-page"; @import "components/article"; @import "components/article-page-control"; @import "components/album"; diff --git a/src/style/variables.scss b/src/style/variables.scss index f4e7937c..5dfdc202 100644 --- a/src/style/variables.scss +++ b/src/style/variables.scss @@ -16,7 +16,7 @@ $embed-video-ratio: 56.25%; $small-margin: .2em; $half-margin: .5em; $single-margin: 1.0em; -$double-margin: 3.0em; +$double-margin: 2.0em; $post-margin: 5.0em; //COLOR @@ -44,15 +44,28 @@ $white-shadow: 0 .1em 0.3em rgba(0, 0, 0, 0.2); $title-font: "PT Serif", Georgia, serif; $text-font: "PT Sans", Arial, sans-serif; -$normal-font-size: 1em; -$bigger-font-size: 1.2em; -$big-font-size: 1.75em; -$smaller-font-size: .8em; +$default-font-size: 14pt; +$smaller-font-size: 0.9rem; +$normal-font-size: 1.0rem; +$bigger-font-size: 1.2rem; +$big-font-size: 1.5rem; +$biggest-font-size: 2.5rem; //MENU $menu-thickness: 2.5em; -$menu-colored-mark-thickness: 4px; +$menu-colored-mark-thickness: 2px; $menu-inactive-item-color: hsl(0, 0%, 50%); //ANIMATION -$short-duration: 0.3s; \ No newline at end of file +$short-duration: 0.3s; + + +@media (max-width: $mobile-width) { + //FONTS + $default-font-size: 14pt; + $smaller-font-size: 0.9rem; + $normal-font-size: 1.0rem; + $bigger-font-size: 1.1rem; + $big-font-size: 1.3rem; + $biggest-font-size: 2.0rem; +} diff --git a/src/templates/components/cloud-page.html b/src/templates/components/cloud-page.html new file mode 100644 index 00000000..1a162cf5 --- /dev/null +++ b/src/templates/components/cloud-page.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/src/templates/main.html b/src/templates/main.html index ff8cf9d6..23ea8d98 100644 --- a/src/templates/main.html +++ b/src/templates/main.html @@ -48,7 +48,11 @@ //ga('send', 'pageview'); } + + + +{{{{raw-helper}}}}
@@ -62,7 +66,6 @@ @@ -93,4 +96,6 @@ })(); +{{{{/raw-helper}}}} +