diff --git a/private/app/App.js b/private/app/App.js index 2e6912a..5d68bcf 100644 --- a/private/app/App.js +++ b/private/app/App.js @@ -26,7 +26,8 @@ const Configuration = require('./../config/Configuration'), DatabaseConnection = require('./../database/DatabaseConnection'), Server = require('./../server/Server'), - Email = require('./../email/Email') + Email = require('./../email/Email'), + CacheStore = require('./../cache/CacheStore') ; class App { @@ -35,9 +36,11 @@ class App { this.database = new DatabaseConnection(this); this.server = new Server(this); this.email = new Email(this); + this.store = new CacheStore(this); } getConfig() { return this.config; } + getCacheStore() {return this.store;} getDiscord() { return this.discord; } getDatabase() { return this.database; } getPalaise() { return this.palaise; } diff --git a/private/cache/CacheStore.js b/private/cache/CacheStore.js new file mode 100644 index 0000000..5b23211 --- /dev/null +++ b/private/cache/CacheStore.js @@ -0,0 +1,68 @@ +// Copyright (c) 2018 Dominic Masters +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +const NodeCache = require('node-cache'); + +class CacheStore { + constructor(app, ttl) { + if(!ttl) ttl = 60*60; + + this.app = app; + this.store = new NodeCache({ + stdTTL: ttl, + checkperiod: ttl * 0.2, + useClones: false + }); + } + + getApp() {return this.app;} + getStore() {return this.store;} + getDatabase() {return this.app.getDatabase();} + + async get(key, prom) { + let value = this.store.get(key); + if(typeof value !== typeof undefined) return value; + + value = await prom(); + this.store.set(key, value); + return value; + } + + del(keysOrKey) { + let keys = keysOrKey; + if(!Array.isArray(keysOrKey)) keys = [keys]; + this.store.del(keys); + } + + //Database related stores + async getFromDatabase(key, query, params, method) { + if(typeof params === typeof undefined) params = {}; + if(typeof method === typeof undefined) method = "any"; + + return await this.get(key, async () => { + return await this.database[method](query, params); + }); + } +} + +module.exports = CacheStore; diff --git a/public/components/section/blog/article/ArticleGridSection.jsx b/public/components/section/blog/article/ArticleGridSection.jsx new file mode 100644 index 0000000..31b6c38 --- /dev/null +++ b/public/components/section/blog/article/ArticleGridSection.jsx @@ -0,0 +1,50 @@ +// Copyright (c) 2018 Dominic Masters +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import React from 'react'; + +import { PageBoundary } from '@components/page/Page'; + +import Section from '@sections/Section'; + +import ArticleThumbnail from '@objects/blog/article/ArticleThumbnail'; + +import Styles from './ArticleGridSection.scss'; + +export default props => { + let { className, articles } = props; + articles = articles || []; + + return ( +
+ + { articles.map((article,index) => { + return + }) } + +
+ ); +} diff --git a/public/components/section/blog/article/ArticleGridSection.scss b/public/components/section/blog/article/ArticleGridSection.scss new file mode 100644 index 0000000..ff4a488 --- /dev/null +++ b/public/components/section/blog/article/ArticleGridSection.scss @@ -0,0 +1,33 @@ +/* + * Article Grid Section + * Styles for the Article Grid Section, aligns sets of articles out in a nice + * grid with some additional styling depending on the count etc. + * + * Version: + * 1.0.0 - 2018/10/31 + */ +@import '~@styles/global'; + +.c-article-grid { + &__grid { + display: flex; + flex-wrap: wrap; + align-items: stretch; + } + + &__article { + width: 100%; + } + + @include t-media-query($s-xsmall-up) { + &__article { + width: 50%; + } + } + + @include t-media-query($s-small-up) { + &__article { + width: (100%/3); + } + } +} diff --git a/public/components/section/blog/article/FeaturedArticleSection.jsx b/public/components/section/blog/article/FeaturedArticleSection.jsx index 6f36f8b..9094c9d 100644 --- a/public/components/section/blog/article/FeaturedArticleSection.jsx +++ b/public/components/section/blog/article/FeaturedArticleSection.jsx @@ -22,8 +22,9 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import React from 'react'; +import { NavLink } from 'react-router-dom'; - +import { withLanguage } from '@public/language/Language'; import BannerImageSection from '@sections/image/banner/BannerImageSection'; import { PageBoundary } from '@components/page/Page'; @@ -34,8 +35,8 @@ import { Title, Paragraph } from '@objects/typography/Typography'; import Styles from './FeaturedArticleSection.scss'; -export default props => { - let { article } = props; +export default withLanguage(props => { + let { article, lang } = props; return ( { - { article.title } + <NavLink to={ article.url } children={ article.title} /> - { article.shortDescription } + { article.shortDescription }  + ); -}; +}); diff --git a/public/components/section/image/ImageSection.jsx b/public/components/section/image/ImageSection.jsx index 648a475..b74a1b0 100644 --- a/public/components/section/image/ImageSection.jsx +++ b/public/components/section/image/ImageSection.jsx @@ -22,12 +22,13 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import React from 'react'; -import Styles from './ImageSection.scss'; +import { NavLink } from 'react-router-dom'; import Section from './../Section'; - import Image from '@objects/image/Image'; +import Styles from './ImageSection.scss'; + export default props => { let sectionProps = {...props}; let imageProps = {...props}; @@ -35,6 +36,7 @@ export default props => { let { image, background, children, className, imageClassName } = props; ["children", "background", "loadable", "imageClassName"].forEach(e => delete sectionProps[e]); + ["image", "full", "children", "background", "imageClassName"].forEach(e => delete imageProps[e]); let clazz = "c-image-section"; @@ -49,6 +51,7 @@ export default props => { { image }
+ { children }
diff --git a/public/language/en-AU.jsx b/public/language/en-AU.jsx index 27f6f8b..336377f 100644 --- a/public/language/en-AU.jsx +++ b/public/language/en-AU.jsx @@ -24,6 +24,12 @@ export default { } }, + "blog": { + "article": { + "readMore": "Read More" + } + }, + "pages": { "home": { "banner": { diff --git a/public/objects/blog/article/ArticleThumbnail.jsx b/public/objects/blog/article/ArticleThumbnail.jsx new file mode 100644 index 0000000..4c19e14 --- /dev/null +++ b/public/objects/blog/article/ArticleThumbnail.jsx @@ -0,0 +1,78 @@ +// Copyright (c) 2018 Dominic Masters +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import React from 'react'; +import { NavLink } from 'react-router-dom'; + +import { withLanguage } from '@public/language/Language'; + +import Image from '@objects/image/Image'; +import ContentBox from '@objects/content/box/ContentBox'; +import FloatingContentBox from '@objects/content/box/FloatingContentBox'; +import { Heading2, Paragraph } from '@objects/typography/Typography'; + +import Styles from './ArticleThumbnail.scss'; + +export default withLanguage(props => { + let { className, article, lang, index } = props; + index = (index || 0)%4; + + let pos = ""; + if(index/2.0 == Math.round(index/2)) { + pos += "top"; + pos += index == 2 ? " left" : " right"; + } else { + pos += "bottom"; + pos += index == 1 ? " left" : " right"; + } + + return ( +
+ + {/* Image */} + {article.title} + + {/* Title */} + + + { article.title } + + + + + + + { article.shortDescription } + + + + { lang.blog.article.readMore } + + +
+ ); +}); diff --git a/public/objects/blog/article/ArticleThumbnail.scss b/public/objects/blog/article/ArticleThumbnail.scss new file mode 100644 index 0000000..3afb64a --- /dev/null +++ b/public/objects/blog/article/ArticleThumbnail.scss @@ -0,0 +1,40 @@ +/* + * Article Thumbnail + * Styles for displaying an article in a short thumbnail object that can be + * put in the usual places. + * + * Version: + * 1.0.0 - 2018/10/31 + */ +@import '~@styles/global'; + +.o-article-thumbnail { + padding-bottom: 1.5em; + + &__header { + display: block; + position: relative; + } + + &__image { + display: block; + width: 100%; + } + + &__title { + margin: 0; + width: 100%; + + &-box { + transition: all 0.3s $s-animation--ease-out; + + .o-article-thumbnail__header:hover & { + transform: translateY(-7.5%); + } + } + } + + &__content { + padding: 0 0.5em; + } +} diff --git a/public/pages/blog/BlogPage.jsx b/public/pages/blog/BlogPage.jsx index 907ef47..7ccd57c 100644 --- a/public/pages/blog/BlogPage.jsx +++ b/public/pages/blog/BlogPage.jsx @@ -27,6 +27,7 @@ import { withLanguage } from '@public/language/Language'; import Page, { PageBoundary } from '@components/page/Page'; import FeaturedArticleSection from '@sections/blog/article/FeaturedArticleSection'; +import ArticleGridSection from '@sections/blog/article/ArticleGridSection'; const TestBlogData = { handle: "test-blog", @@ -43,6 +44,7 @@ export default withLanguage(props => { {/* First (Featured) Blog */} + ); });