├── CHANGELOG.md ├── content ├── logs │ └── .gitkeep ├── posts │ └── .gitkeep ├── themes │ └── blablabla │ │ ├── assets │ │ ├── js │ │ │ └── .gitkeep │ │ ├── css │ │ │ └── .gitkeep │ │ ├── scss │ │ │ ├── _states.scss │ │ │ ├── _settings.scss │ │ │ ├── main.scss │ │ │ ├── layouts │ │ │ │ ├── _footer.scss │ │ │ │ ├── _header.scss │ │ │ │ ├── _author.scss │ │ │ │ └── _page.scss │ │ │ ├── modules │ │ │ │ ├── _utils.scss │ │ │ │ ├── _buttons.scss │ │ │ │ ├── _icons.scss │ │ │ │ ├── _pagination.scss │ │ │ │ └── _highlight.scss │ │ │ ├── _helpers.scss │ │ │ ├── _mixins.scss │ │ │ ├── _animations.scss │ │ │ ├── _base.scss │ │ │ ├── _media_queries.scss │ │ │ └── _normalize.scss │ │ ├── fonts │ │ │ ├── ionicons.eot │ │ │ ├── ionicons.ttf │ │ │ └── ionicons.woff │ │ └── img │ │ │ └── loader.svg │ │ ├── Error.js │ │ ├── Gravatar.js │ │ ├── Tag.js │ │ ├── HeaderSingle.js │ │ ├── Loader.js │ │ ├── Tags.js │ │ ├── GoogleAnalytics.js │ │ ├── TagPage.js │ │ ├── Footer.js │ │ ├── Header.js │ │ ├── Paginator.js │ │ ├── PostListElement.js │ │ ├── Disqus.js │ │ ├── Html.js │ │ ├── PostList.js │ │ ├── App.js │ │ └── Single.js ├── favicon.ico ├── images │ └── posts │ │ └── home.png ├── pages │ └── a-static-page.md └── drafts │ └── welcome-to-morpheus.md ├── .gitattributes ├── config ├── test.js ├── production.js ├── development.js └── default.js ├── branding ├── morpheus 16x16.png ├── morpheus wide dark.png ├── morpheus wide light.png ├── morpheus wide nobg.png ├── morpheus center dark.png ├── morpheus center light.png ├── morpheus center nobg.png ├── morpheus logo square.png ├── _vars.scss ├── morpheus logo square.svg ├── morpheus 16x16.svg ├── morpheus wide nobg.svg ├── morpheus center nobg.svg ├── morpheus center light.svg └── morpheus wide dark.svg ├── .travis.yml ├── server ├── services │ ├── index.js │ ├── rss.js │ └── content.js ├── errors │ ├── index.js │ ├── not-found.js │ └── internal-error.js ├── file-system-repository-strategy │ ├── index.js │ └── content-repository.js ├── middlewares │ ├── request-logger.js │ ├── nocache.js │ ├── robots.js │ ├── fluxible-context.js │ ├── sanitize.js │ ├── ssl-redirection.js │ ├── navigation.js │ ├── not-found.js │ └── errors.js ├── abstract-repository.js ├── utils.js ├── logger-factory.js ├── express-loader.js └── routes │ └── index.js ├── test └── index.js ├── shared ├── mixins │ └── InitialStateMixin.js ├── config.js ├── stores │ ├── ContentStore.js │ ├── ContentListStore.js │ ├── MetaStore.js │ └── ApplicationStore.js ├── context.js └── actions │ └── ContentActions.js ├── server.js ├── client ├── utils.js ├── client.js └── client-routes.js ├── .jshintrc ├── .editorconfig ├── webpack.config.production.config.js ├── webpack.config.development.js ├── LICENSE ├── morpheus.js ├── .gitignore ├── .jscsrc ├── CONTRIBUTING.md ├── package.json ├── gulpfile.js └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /content/logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /content/posts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.scss linguist-vendored 2 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/js/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/css/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /config/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/_states.scss: -------------------------------------------------------------------------------- 1 | //states here 2 | -------------------------------------------------------------------------------- /config/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /content/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/content/favicon.ico -------------------------------------------------------------------------------- /branding/morpheus 16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/branding/morpheus 16x16.png -------------------------------------------------------------------------------- /content/images/posts/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/content/images/posts/home.png -------------------------------------------------------------------------------- /branding/morpheus wide dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/branding/morpheus wide dark.png -------------------------------------------------------------------------------- /branding/morpheus wide light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/branding/morpheus wide light.png -------------------------------------------------------------------------------- /branding/morpheus wide nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/branding/morpheus wide nobg.png -------------------------------------------------------------------------------- /branding/morpheus center dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/branding/morpheus center dark.png -------------------------------------------------------------------------------- /branding/morpheus center light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/branding/morpheus center light.png -------------------------------------------------------------------------------- /branding/morpheus center nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/branding/morpheus center nobg.png -------------------------------------------------------------------------------- /branding/morpheus logo square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/branding/morpheus logo square.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_install: 5 | - 'gem update --system' 6 | - 'gem install sass' 7 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/content/themes/blablabla/assets/fonts/ionicons.eot -------------------------------------------------------------------------------- /content/themes/blablabla/assets/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/content/themes/blablabla/assets/fonts/ionicons.ttf -------------------------------------------------------------------------------- /content/themes/blablabla/assets/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/morpheus/HEAD/content/themes/blablabla/assets/fonts/ionicons.woff -------------------------------------------------------------------------------- /server/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var contentService = require('./content'); 4 | 5 | module.exports = function(config){ 6 | return{ 7 | content: contentService(config) 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | 5 | describe('index', function() { 6 | it('I should write some test', function() { 7 | (true).should.be.true; 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /server/errors/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var NotFound = require('./not-found'); 4 | var InternalError = require('./internal-error'); 5 | 6 | module.exports.NotFound = NotFound; 7 | module.exports.InternalError = InternalError; 8 | -------------------------------------------------------------------------------- /server/file-system-repository-strategy/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ContentRepository = require('./content-repository'); 4 | module.exports = function (){ 5 | return { 6 | content: new ContentRepository() 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /server/middlewares/request-logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(log) { 4 | return function(req, res, next) { 5 | log.info([req.url, req.method, res.statusCode].join(' - ')); 6 | return next(); 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /server/middlewares/nocache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(req, res, next) { 4 | res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); 5 | res.header('Expires', '-1'); 6 | res.header('Pragma', 'no-cache'); 7 | next(); 8 | }; 9 | -------------------------------------------------------------------------------- /content/themes/blablabla/Error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var NotFound = React.createClass({ 5 | render: function(){ 6 | return ( 7 |
Error: {this.props.error.message}
8 | ) 9 | } 10 | }); 11 | 12 | module.exports = NotFound; 13 | -------------------------------------------------------------------------------- /shared/mixins/InitialStateMixin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var InitialStateMixin = { 4 | getInitialState: function() { 5 | return this.props; 6 | }, 7 | onChange: function() { 8 | this.setState(this.getState()); 9 | } 10 | }; 11 | 12 | module.exports = InitialStateMixin; 13 | -------------------------------------------------------------------------------- /server/middlewares/robots.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(req, res, next) { 4 | if (req.url === '/robots.txt') { 5 | res.type('text/plain'); 6 | res.send('User-agent: *\nDisallow: /admin/\nSitemap: /sitemap.xml'); 7 | } else { 8 | next(); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('node-jsx').install(); 4 | var morpheus = require('./morpheus'); 5 | 6 | morpheus.run(function(info) { 7 | morpheus.logger.info('Worker %s is running morpheus@%s in %s mode on port %d', process.pid, info.version, process.env.NODE_ENV || 'development', info.port); 8 | }); 9 | -------------------------------------------------------------------------------- /client/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | intersperse: function (arr, sep) { 5 | if (arr.length === 0) { 6 | return []; 7 | } 8 | return arr.slice(1).reduce(function(xs, x, i) { // jshint ignore:line 9 | return xs.concat([sep, x]); 10 | }, [arr[0]]); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /server/middlewares/fluxible-context.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(appContext){ 4 | return function(req, res, next) { // jshint ignore:line 5 | res.locals.fluxibleApp = appContext; 6 | res.locals.context = appContext.createContext({ 7 | req: req 8 | }); 9 | next(); 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /server/errors/not-found.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | 5 | function NotFound(message) { 6 | Error.call(this); 7 | this.stack = new Error().stack; 8 | this.message = message || 'not found'; 9 | this.statusCode = 404; 10 | } 11 | 12 | util.inherits(NotFound, Error); 13 | 14 | module.exports = NotFound; 15 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/_settings.scss: -------------------------------------------------------------------------------- 1 | // Base font size in used in _mixins.scss 2 | $base-root-size: 16; 3 | 4 | //colors https://github.com/mrmrs/colors/blob/master/sass/_variables.scss 5 | $blue: #00bcd4; 6 | $light-black: #111111; 7 | $light-black-clear: #2e2e2e; 8 | $silver: #dddddd; 9 | $gray:#aaaaaa; 10 | $white:#ffffff; 11 | -------------------------------------------------------------------------------- /server/middlewares/sanitize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(req, res, next) { // jshint ignore:line 4 | if (req.path.indexOf('/api/') === 0) { 5 | return next(); 6 | } 7 | if (/[A-Z]/.test(req.path)) { 8 | res.redirect(301, req.url.replace(req.path, req.path.toLowerCase())); 9 | } else { 10 | next(); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /server/errors/internal-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | 5 | function InternalError(message) { 6 | Error.call(this); 7 | this.stack = new Error().stack; 8 | this.message = message || 'internal server error'; 9 | this.statusCode = 404; 10 | } 11 | 12 | util.inherits(InternalError, Error); 13 | 14 | module.exports = InternalError; 15 | -------------------------------------------------------------------------------- /server/middlewares/ssl-redirection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var morpheus = require('../../morpheus'); 4 | 5 | module.exports = function(req, res, next) { 6 | if (morpheus.config.useSSL) { 7 | if (req.headers['x-forwarded-proto'] === 'http') { 8 | return res.redirect(301, 'https://' + req.headers.host + req.path); 9 | } else { 10 | return next(); 11 | } 12 | }else{ 13 | return next(); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "trailing": true, 11 | "quotmark": "single", 12 | "strict": true, 13 | "multistr": true, 14 | "debug": false, 15 | "forin": true, 16 | "undef": true, 17 | "plusplus": true, 18 | "eqeqeq": true, 19 | "validthis": false, 20 | "unused": true, 21 | "jasmine": true 22 | } 23 | -------------------------------------------------------------------------------- /server/middlewares/navigation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var navigateAction = require('flux-router-component').navigateAction; 4 | 5 | module.exports = function(req, res, next) { // jshint ignore:line 6 | if (req.path.indexOf('/api/') === 0) { 7 | return next(); 8 | } 9 | res.locals.context.getActionContext().executeAction(navigateAction, { 10 | url: req.url 11 | }, function(err) { 12 | if (err) { 13 | next(err); 14 | } else { 15 | next(); 16 | } 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /content/themes/blablabla/Gravatar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var gravatar = require('gravatar'); 5 | 6 | var Gravatar = React.createClass({ 7 | propTypes:{ 8 | email: React.PropTypes.string.isRequired 9 | }, 10 | render: function() { 11 | var img = gravatar.url(this.props.email, {s:250}, true); 12 | return ( 13 | Author image 14 | ); 15 | } 16 | }); 17 | 18 | module.exports = Gravatar; 19 | -------------------------------------------------------------------------------- /content/themes/blablabla/Tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | //var NavLink = require('flux-router-component').NavLink; 5 | 6 | var Tag = React.createClass({ 7 | propTypes:{ 8 | tag: React.PropTypes.object.isRequired 9 | }, 10 | render: function() { 11 | return ( 12 | {this.props.tag.name} 13 | //{this.props.tag.name} 14 | ); 15 | } 16 | }); 17 | 18 | module.exports = Tag; 19 | -------------------------------------------------------------------------------- /client/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var appContext = require('../shared/context'); 5 | var dehydratedState = window.Morpheus; // Sent from the server 6 | 7 | window.React = React; // For chrome dev tool support 8 | appContext.rehydrate(dehydratedState, function(err, context) { 9 | if (err) { 10 | throw err; 11 | } 12 | var mountNode = document.body; 13 | React.render(appContext.getAppComponent()({ 14 | context: context.getComponentContext(), 15 | enableScroll: false 16 | }), mountNode, function() {}); 17 | }); 18 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/main.scss: -------------------------------------------------------------------------------- 1 | /* 2 | inspired by: 3 | https://github.com/minamarkham/sassy-starter/ 4 | https://github.com/kachunchau/smacss-boilerplate 5 | https://github.com/davidrapson/scss-toolkit 6 | */ 7 | @import 8 | 'settings', 9 | 'mixins', 10 | 'base', 11 | 'helpers', 12 | 'animations', 13 | 14 | 'modules/buttons', 15 | 'modules/icons', 16 | 'modules/pagination', 17 | 'modules/utils', 18 | 'modules/highlight', 19 | 20 | 'layouts/page', 21 | 'layouts/author', 22 | 'layouts/header', 23 | 'layouts/footer', 24 | 25 | 'media_queries', 26 | 27 | 'states'; 28 | -------------------------------------------------------------------------------- /branding/_vars.scss: -------------------------------------------------------------------------------- 1 | 2 | /* Morpheus branding variables. */ 3 | 4 | // Brand fonts 5 | $brand-font: "Ubuntu Condensed", "Avenir Next Condensed", sans-serif; 6 | $header-font: "Source Sans Pro", "Open Sans", "Avenir Next", sans-serif; 7 | $body-font: "Source Serif Pro", "Merriweather", "Georgia", serif; 8 | $code-font: "Source Code Pro", "Input Sans", "Inconsolata", monospace; 9 | 10 | // Brand colors 11 | $brand-red: #EC5334; 12 | $brand-blue: #81426E; 13 | $brand-purple: #81426E; 14 | $brand-dark: #434343; 15 | $brand-light: #FCFCF5; 16 | 17 | // UI colors 18 | $font-color: $brand-dark; 19 | $quiet-font-color: #999; 20 | $background-color: $brand-light; 21 | -------------------------------------------------------------------------------- /webpack.config.production.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | process.env.NODE_ENV = 'development'; 5 | var configuration = require('./shared/config'); 6 | 7 | module.exports = { 8 | entry: './client/client.js', 9 | output: { 10 | filename: 'bundle.js', 11 | path: path.join(__dirname, 'content/themes/'+ configuration.theme +'/assets/dist') 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.js$/, 16 | exclude: /node_modules/, 17 | loader: '6to5-loader' 18 | }, 19 | { 20 | test: /\.json$/, 21 | exclude: /node_modules/, 22 | loader: 'json-loader' 23 | }] 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /content/themes/blablabla/HeaderSingle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var NavLink = require('flux-router-component').NavLink; 5 | 6 | var Header = React.createClass({ 7 | propTypes:{ 8 | context: React.PropTypes.object.isRequired 9 | }, 10 | render: function() { 11 | return ( 12 |
13 | 16 |
17 | ); 18 | } 19 | }); 20 | 21 | module.exports = Header; 22 | -------------------------------------------------------------------------------- /webpack.config.development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | process.env.NODE_ENV = 'development'; 5 | var configuration = require('./shared/config'); 6 | 7 | module.exports = { 8 | entry: './client/client.js', 9 | output: { 10 | filename: 'bundle.js', 11 | path: path.join(__dirname, 'content/themes/'+ configuration.theme +'/assets/dist') 12 | }, 13 | watch: true, 14 | module: { 15 | loaders: [{ 16 | test: /\.js$/, 17 | exclude: /node_modules/, 18 | loader: '6to5-loader' 19 | }, 20 | { 21 | test: /\.json$/, 22 | exclude: /node_modules/, 23 | loader: 'json-loader' 24 | }] 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/layouts/_footer.scss: -------------------------------------------------------------------------------- 1 | .site-footer { 2 | position: relative; 3 | margin: 2rem 0 0; 4 | padding: 0.5rem 15px; 5 | border-top: $silver 1px solid; 6 | font-family: "Open Sans", sans-serif; 7 | font-size: rem-calc(13); 8 | line-height: 1.75rem; 9 | color: $gray; 10 | section { 11 | @include vertical-align(); 12 | } 13 | } 14 | .site-footer a { 15 | color: $gray; 16 | text-decoration: none; 17 | font-weight: bold; 18 | } 19 | .site-footer a:hover { 20 | text-decoration: none; 21 | color:$blue; 22 | } 23 | .poweredby { 24 | display: block; 25 | width: 45%; 26 | float: right; 27 | text-align: right; 28 | } 29 | .copyright { 30 | display: block; 31 | width: 45%; 32 | float: left; 33 | } 34 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/modules/_utils.scss: -------------------------------------------------------------------------------- 1 | /* Clears shit */ 2 | .clearfix:before, 3 | .clearfix:after { 4 | content: " "; 5 | display: table; 6 | } 7 | .clearfix:after { 8 | clear: both; 9 | } 10 | .clearfix { 11 | *zoom: 1; 12 | 13 | } 14 | /* Hides shit */ 15 | .hidden { 16 | text-indent: -9999px; 17 | visibility: hidden; 18 | display: none; 19 | 20 | } 21 | /* Creates a responsive wrapper that makes our content scale nicely */ 22 | .inner { 23 | position: relative; 24 | width: 80%; 25 | max-width: 710px; 26 | margin: 0 auto; 27 | 28 | } 29 | /* Centres vertically yo. (IE8+) */ 30 | .vertical { 31 | display: table-cell; 32 | vertical-align: middle; 33 | 34 | } 35 | 36 | .text-center { 37 | text-align: center; 38 | } 39 | -------------------------------------------------------------------------------- /shared/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assign = require('object-assign'); 4 | var defaultConfig = require('../config/default'); 5 | var env = process.env.NODE_ENV || 'development'; 6 | var envConfig = require('../config/' + env); 7 | var path = require('path'); 8 | var pkg = require('../package.json'); 9 | 10 | function buildConfig(isNode) { 11 | var data = assign(defaultConfig, envConfig); 12 | data.env = process.env.NODE_ENV; 13 | data.version = pkg.version; 14 | if (isNode) { 15 | data.appRoot = process.cwd(); 16 | data.log.file = path.resolve(data.appRoot, 'content/logs', data.log.file); 17 | data.contentPath = path.resolve(data.appRoot, 'content'); 18 | } 19 | return data; 20 | } 21 | 22 | module.exports = buildConfig(typeof window === 'undefined'); 23 | -------------------------------------------------------------------------------- /content/themes/blablabla/Loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var Loader = React.createClass({ 6 | propTypes:{ 7 | class: React.PropTypes.string.isRequired 8 | }, 9 | getInitialState: function() { 10 | return { 11 | class: 'hidden' 12 | }; 13 | }, 14 | componentDidMount: function () { 15 | this.updateState(this.props); 16 | }, 17 | componentWillReceiveProps: function (nextProps) { 18 | this.updateState(nextProps); 19 | }, 20 | updateState: function (props) { 21 | this.setState({ 22 | class: ['loader', props.class].join(' ') }); 23 | }, 24 | render: function() { 25 | return ( 26 |
< /div> 27 | ); 28 | } 29 | }); 30 | 31 | module.exports = Loader; 32 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/_helpers.scss: -------------------------------------------------------------------------------- 1 | %center { 2 | margin-right: auto; 3 | margin-left: auto; 4 | } 5 | 6 | %text-center { 7 | text-align:center; 8 | } 9 | 10 | %clearfix { 11 | &::before, 12 | &::after { 13 | content: ""; 14 | display: table; 15 | } 16 | &::after { 17 | clear: both; 18 | } 19 | } 20 | 21 | .text-center{ 22 | @extend %text-center; 23 | } 24 | 25 | @function rem-calc($size, $context: $base-root-size) { 26 | @return ($size / $base-root-size) + rem; 27 | } 28 | 29 | .videoWrapper { 30 | position: relative; 31 | padding-bottom: 56.25%; /* 16:9 */ 32 | padding-top: 25px; 33 | height: 0; 34 | } 35 | .videoWrapper iframe { 36 | position: absolute; 37 | top: -20px; 38 | left: 0; 39 | width: 100%; 40 | height: 100%; 41 | } 42 | -------------------------------------------------------------------------------- /client/client-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../shared/config'); 4 | 5 | function getRoutes() { 6 | var routes = { 7 | home: { 8 | path: '/', 9 | method: 'get', 10 | page: 'home' 11 | }, 12 | static: { 13 | path: '/:title/', 14 | method: 'get', 15 | page: 'static' 16 | }, 17 | tag: { 18 | path: '/tag/:tag/', 19 | method: 'get', 20 | page: 'tag' 21 | }, 22 | page: { 23 | path: '/page/:page/', 24 | method: 'get', 25 | page: 'page' 26 | } 27 | }; 28 | 29 | if (config.permalinkStructure !== '/:title/') { 30 | routes.post = { 31 | path: config.permalinkStructure, 32 | method: 'get', 33 | page: 'post' 34 | }; 35 | } 36 | return routes; 37 | } 38 | 39 | module.exports = getRoutes(); 40 | -------------------------------------------------------------------------------- /content/themes/blablabla/Tags.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Tag = require('./Tag'); 5 | var clientUtils = require('../../../client/utils'); 6 | 7 | var Tags = React.createClass({ 8 | propTypes:{ 9 | tags: React.PropTypes.array.isRequired, 10 | context: React.PropTypes.object.isRequired 11 | }, 12 | render: function() { 13 | var tags = []; 14 | var pre = ''; 15 | this.props.tags.forEach(function(tag, index){ 16 | tags.push(); 17 | }.bind(this)); 18 | tags = clientUtils.intersperse(tags, ', '); 19 | if (this.props.tags.length > 0) { 20 | pre = on ; 21 | } 22 | return ( 23 |
24 | {pre} 25 | {tags} 26 |
27 | ); 28 | } 29 | }); 30 | 31 | module.exports = Tags; 32 | -------------------------------------------------------------------------------- /content/themes/blablabla/GoogleAnalytics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var added = false; 5 | 6 | var GoogleAnalytics = React.createClass({ 7 | propTypes:{ 8 | code: React.PropTypes.string.isRequired 9 | }, 10 | componentDidMount: function() { 11 | if (!added){ 12 | return this.addScript(); 13 | } 14 | }, 15 | addScript: function() { 16 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 17 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 18 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 19 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 20 | 21 | ga('create', this.props.code, 'auto'); // Replace with your property ID. 22 | }, 23 | render: function() { 24 | return null; 25 | } 26 | }); 27 | 28 | module.exports = GoogleAnalytics; 29 | -------------------------------------------------------------------------------- /server/abstract-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function throwMethodUndefined(methodName) { 4 | throw new Error('The method ' + methodName + ' is not defined! Please overridde it.'); 5 | } 6 | 7 | function AbstractRepository() {} 8 | 9 | AbstractRepository.prototype.find = function(options) { //jshint ignore:line 10 | throwMethodUndefined('find'); 11 | }; 12 | 13 | AbstractRepository.prototype.findOne = function(options) { //jshint ignore:line 14 | throwMethodUndefined('findOne'); 15 | }; 16 | 17 | AbstractRepository.prototype.findPage = function(options) { //jshint ignore:line 18 | throwMethodUndefined('findPage'); 19 | }; 20 | 21 | AbstractRepository.prototype.findTag = function(options) { //jshint ignore:line 22 | throwMethodUndefined('findTag'); 23 | }; 24 | 25 | AbstractRepository.prototype.getPostsForFeed = function(options) { //jshint ignore:line 26 | throwMethodUndefined('getPostsForFeed'); 27 | }; 28 | 29 | module.exports = AbstractRepository; 30 | -------------------------------------------------------------------------------- /shared/stores/ContentStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var createStore = require('fluxible/utils/createStore'); 4 | 5 | var ContentStore = createStore({ 6 | storeName: 'ContentStore', 7 | handlers: { 8 | 'GET_CONTENT_SUCCESS': 'getContentSuccess', 9 | 'RESET_CONTENT': 'resetContent' 10 | 11 | }, 12 | initialize: function(dispatcher) { //jshint ignore:line 13 | this.content = {}; 14 | }, 15 | resetContent: function(){ 16 | this.initialize(); 17 | this.emitChange(); 18 | }, 19 | getContentSuccess: function(content) { 20 | this.content = content; 21 | this.emitChange(); 22 | }, 23 | getAll: function() { 24 | return this.posts; 25 | }, 26 | getState: function() { 27 | return { 28 | content: this.content 29 | }; 30 | }, 31 | dehydrate: function() { 32 | return this.getState(); 33 | }, 34 | rehydrate: function(state) { 35 | this.content = state.content; 36 | } 37 | }); 38 | 39 | module.exports = ContentStore; 40 | -------------------------------------------------------------------------------- /shared/stores/ContentListStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var createStore = require('fluxible/utils/createStore'); 4 | 5 | var ContentListStore = createStore({ 6 | storeName: 'ContentListStore', 7 | handlers: { 8 | 'GET_CONTENT_LIST_SUCCESS': 'getContentListSuccess', 9 | 'RESET_CONTENT_LIST': 'resetContentList' 10 | 11 | }, 12 | initialize: function(dispatcher) { //jshint ignore:line 13 | this.contentList = null; 14 | }, 15 | resetContentList: function(){ 16 | this.initialize(); 17 | this.emitChange(); 18 | }, 19 | getContentListSuccess: function(contentList) { 20 | this.contentList = contentList; 21 | this.emitChange(); 22 | }, 23 | getState: function() { 24 | return { 25 | contentList: this.contentList 26 | }; 27 | }, 28 | dehydrate: function() { 29 | return this.getState(); 30 | }, 31 | rehydrate: function(state) { 32 | this.contentList = state.contentList; 33 | } 34 | }); 35 | 36 | module.exports = ContentListStore; 37 | -------------------------------------------------------------------------------- /content/themes/blablabla/TagPage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ContentListStore = require('../../../shared/stores/ContentListStore'); 5 | var HeaderSingle = require('./HeaderSingle'); 6 | var Footer = require('./Footer'); 7 | var StoreMixin = require('fluxible').StoreMixin; 8 | 9 | 10 | var Single = React.createClass({ 11 | mixins: [StoreMixin], 12 | statics: { 13 | storeListeners: { 14 | _onChange: [ContentListStore] 15 | } 16 | }, 17 | propTypes:{ 18 | tag: React.PropTypes.object.isRequired 19 | }, 20 | _onChange: function() { 21 | this.setState({}); 22 | }, 23 | render: function() { 24 | return ( 25 |
26 | 27 |
28 |
29 | this page has to be implemented 30 |
31 |
32 |
33 |
34 | ); 35 | } 36 | }); 37 | 38 | module.exports = Single; 39 | -------------------------------------------------------------------------------- /config/default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = { 5 | log: { 6 | level: 'debug', 7 | file: 'morpheus.log', 8 | }, 9 | 'repositoryStrategy': { 10 | type: 'file-system-repository-strategy' 11 | }, 12 | debug: true, //useful for seeing some logs in the browser console 13 | permalinkStructure:'/:year/:month/:day/:title/', //you can also use /:title/ or '/:year/:month/:title/' 14 | postPerPage: 3, // number of posts per page 15 | siteUrl: 'http://localhost:3000', // the url of your website 16 | useSSL: false, // if true it redirects all incoming requests to the https url 17 | siteTitle: 'Morpheus', 18 | theme: 'blablabla', // currently used theme 19 | siteDescription: '- say hi to the next generation web publishing platform -', 20 | port: 3000, 21 | ip: '127.0.0.1', 22 | authors: { 23 | 'youreamail@yourwebsite.something': { 24 | meta: 'I really like to write :)' 25 | } 26 | }, 27 | googleAnalytics : 'UA-XXXXX', //your google analytics tracking code 28 | disqusComments : '' //your disqus shortname 29 | }; 30 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | //box Sizing 2 | @mixin box-sizing ($type: border-box) { 3 | //content-box | border-box | inherit 4 | -webkit-box-sizing: $type; 5 | -moz-box-sizing: $type; 6 | box-sizing: $type; 7 | } 8 | @mixin border-box() { 9 | @include box-sizing( border-box ); 10 | } 11 | 12 | //reset border, margin, and padding 13 | @mixin box-reset() { 14 | margin: 0; 15 | padding: 0; 16 | border: 0 none; 17 | } 18 | 19 | //px to rem sizing with px fallout 20 | @mixin font-size($size, $context: $base-root-size) { 21 | font-size: $size + px; 22 | font-size: ($size / $base-root-size) + rem; 23 | } 24 | 25 | //selection 26 | @mixin selection() { 27 | background: #b3d4fc; 28 | text-shadow: none; 29 | } 30 | 31 | //placeholder 32 | @mixin placeholder() { 33 | color: #999; 34 | } 35 | 36 | //vertical align (http://zerosixthree.se/vertical-align-anything-with-just-3-lines-of-css/) 37 | @mixin vertical-align { 38 | position: relative; 39 | top: 50%; 40 | -webkit-transform: translateY(-50%); 41 | -ms-transform: translateY(-50%); 42 | transform: translateY(-50%); 43 | } 44 | -------------------------------------------------------------------------------- /content/themes/blablabla/Footer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var moment = require('moment'); 5 | var ApplicationStore = require('../../../shared/stores/ApplicationStore'); 6 | var cx = require('react/lib/cx'); 7 | 8 | var Footer = React.createClass({ 9 | render: function() { 10 | var classesMap = { 11 | 'footer': true, 12 | 'site-footer': true, 13 | 'clearfix': true 14 | }; 15 | var classes = cx(classesMap); 16 | var year = moment().format('YYYY'); 17 | var siteTitle = this.props.context.getStore(ApplicationStore).getState().globals.siteTitle; 18 | return ( 19 |
20 |
{siteTitle} © {year}
21 |
22 | Built with love using Morpheus 23 | - blablabla theme inspired by Casper 24 |
25 |
26 | ); 27 | } 28 | }); 29 | 30 | module.exports = Footer; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Alessandro Arnodo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/img/loader.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 14 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/layouts/_header.scss: -------------------------------------------------------------------------------- 1 | .main-header { 2 | position: relative; 3 | display: table; 4 | width: 100%; 5 | height: 100%; 6 | margin-bottom: 5rem; 7 | text-align: center; 8 | background: $blue no-repeat center center; 9 | background-size: cover; 10 | overflow: hidden; 11 | min-height: 160px; 12 | max-height: 40%; 13 | } 14 | .main-header .inner { 15 | width: 80%; 16 | } 17 | .main-nav { 18 | position: relative; 19 | padding: 35px 40px; 20 | margin: 0 0 30px; 21 | } 22 | .main-nav a { 23 | text-decoration: none; 24 | font-family: 'Open Sans', sans-serif; 25 | 26 | } 27 | /* Appears in the top right corner of your home page */ 28 | .main-nav.overlay a.blog-logo { 29 | float: right; 30 | font-size: 6rem; 31 | background: none !important; 32 | border: none !important; 33 | 34 | } 35 | .main-nav.overlay a.blog-logo:hover { 36 | color: #ffdc00; 37 | -webkit-animation: rubberBand 1s; 38 | animation: rubberBand 1s; 39 | 40 | } 41 | .blog-logo img { 42 | -webkit-box-sizing: border-box; 43 | -moz-box-sizing: border-box; 44 | box-sizing: border-box; 45 | display: block; 46 | height: 38px; 47 | padding: 1px 0 5px; 48 | width: auto; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /shared/stores/MetaStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var createStore = require('fluxible/utils/createStore'); 4 | var assign = require('object-assign'); 5 | 6 | var MetaStore = createStore({ 7 | storeName: 'MetaStore', 8 | handlers: { 9 | 'SET_META': 'setMeta' 10 | }, 11 | initialize: function(dispatcher) { //jshint ignore:line 12 | this.meta = {}; 13 | }, 14 | setMeta: function(meta) { 15 | this.meta = {}; 16 | this.meta = assign({}, meta); 17 | delete this.meta.content; 18 | delete this.meta.excerpt; 19 | 20 | //meta 21 | this.meta.ogType = this.meta.type === 'post' ? 'article' : 'website'; 22 | this.meta.metaImage = this.meta.metaImage ? this.meta.metaImage : ''; 23 | this.meta.metaTitle = this.meta.metaTitle ? this.meta.metaTitle : ''; 24 | this.meta.metaDescription = this.meta.metaDescription ? this.meta.metaDescription : this.meta.description; 25 | this.emitChange(); 26 | }, 27 | getState: function() { 28 | return { 29 | meta: this.meta 30 | }; 31 | }, 32 | dehydrate: function() { 33 | return this.getState(); 34 | }, 35 | rehydrate: function(state) { 36 | this.meta = state.meta; 37 | this.emitChange(); 38 | } 39 | }); 40 | 41 | module.exports = MetaStore; 42 | -------------------------------------------------------------------------------- /morpheus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expressLoader = require('./server/express-loader'); 4 | var LoggerFactory = require('./server/logger-factory'); 5 | var pkg = require('./package.json'); 6 | var config = require('./shared/config'); 7 | var loggerFactory = new LoggerFactory(config); 8 | var servicesLoader = require('./server/services/'); 9 | 10 | function Morpheus() { 11 | this.config = {}; 12 | this.services = {}; 13 | } 14 | 15 | Morpheus.prototype.init = function() { 16 | this.config = config; 17 | //load services 18 | this.services = servicesLoader(this.config); 19 | }; 20 | 21 | Morpheus.prototype.logger = loggerFactory.create('morpheus'); 22 | 23 | Morpheus.prototype.getLogger = loggerFactory.create.bind(loggerFactory); 24 | 25 | Morpheus.prototype.run = function(callback) { 26 | var that = this; 27 | var expressApp = expressLoader(); 28 | var params = { 29 | config: that.config, 30 | loggerFactory: loggerFactory, 31 | version: pkg.version 32 | }; 33 | expressApp.listen(that.config.port, that.config.ip, function() { 34 | callback({ 35 | version: params.version, 36 | port: that.config.port, 37 | }); 38 | }); 39 | }; 40 | 41 | var morpheus = new Morpheus(); 42 | morpheus.init(); 43 | module.exports = morpheus; 44 | -------------------------------------------------------------------------------- /content/themes/blablabla/Header.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var NavLink = require('flux-router-component').NavLink; 5 | var ApplicationStore = require('../../../shared/stores/ApplicationStore'); 6 | 7 | var Header = React.createClass({ 8 | propTypes:{ 9 | context: React.PropTypes.object.isRequired 10 | }, 11 | render: function() { 12 | var siteGlobal = this.props.context.getStore(ApplicationStore).getState().globals; 13 | var siteUrl = siteGlobal.siteUrl; 14 | var siteTitle = siteGlobal.siteTitle; 15 | var siteDescription = siteGlobal.siteDescription; 16 | return ( 17 |
18 | 22 |
23 |
24 |

{siteTitle}

25 |

{siteDescription}

26 |
27 |
28 |
29 | ); 30 | } 31 | }); 32 | 33 | module.exports = Header; 34 | -------------------------------------------------------------------------------- /content/themes/blablabla/Paginator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var NavLink = require('flux-router-component').NavLink; 5 | 6 | var Paginator = React.createClass({ 7 | propTypes:{ 8 | page: React.PropTypes.string.isRequired, 9 | pageCount: React.PropTypes.number.isRequired, 10 | totalCount: React.PropTypes.number.isRequired, 11 | context:React.PropTypes.object.isRequired 12 | }, 13 | render: function() { 14 | var prev = ''; 15 | var next = ''; 16 | var page = parseInt(this.props.page, 10); 17 | page = isNaN(page) ? 1 : page; 18 | if (page > 1) { 19 | var toPrev = page === 2 ? '/' : '/page/'+(page - 1)+'/'; 20 | prev = ← Newer Posts; 21 | } 22 | if (page < this.props.pageCount) { 23 | var toNext = '/page/'+ (page + 1)+'/'; 24 | next = Older Posts →; 25 | } 26 | return ( 27 | 32 | ); 33 | } 34 | }); 35 | 36 | module.exports = Paginator; 37 | -------------------------------------------------------------------------------- /server/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var downsize = require('downsize'); 4 | var React = require('react'); 5 | var config = require('../shared/config'); 6 | var HtmlComponent = React.createFactory(require('../content/themes/'+ config.theme+ '/Html')); 7 | var marked = require('marked'); 8 | var Promise = require('es6-promise').Promise; // jshint ignore:line 9 | 10 | module.exports = { 11 | detectEnvironment: function() { 12 | return process.env.NODE_ENV || 'development'; 13 | }, 14 | excerpt: function(md) { 15 | var res; 16 | res = md.replace(/<\/?[^>]+>/gi, ''); 17 | res = res.replace(/(\r\n|\n|\r)+/gm, ' '); 18 | return downsize(res, { 19 | 'words': 50 20 | }); 21 | }, 22 | render: function(res, markup, state) { 23 | state = state || res.locals.state; 24 | var html = React.renderToStaticMarkup(HtmlComponent({ //jshint ignore:line 25 | state: state, 26 | markup: markup, 27 | context: res.locals.context.getComponentContext() 28 | })); 29 | res.set({ 30 | 'content-type': 'text/html; charset=utf-8' 31 | }); 32 | res.write('' + html); 33 | res.end(); 34 | }, 35 | md2html: function(md){ 36 | return new Promise(function(resolve, reject) { 37 | marked(md, function (err, content) { 38 | if (err) { 39 | reject(err); 40 | }else{ 41 | resolve(content); 42 | } 43 | }); 44 | }); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /content/themes/blablabla/PostListElement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ReactPropTypes = React.PropTypes; 5 | var NavLink = require('flux-router-component').NavLink; 6 | var Tags = require('./Tags'); 7 | var Gravatar = require('./Gravatar'); 8 | 9 | var PostListElement = React.createClass({ 10 | propTypes: { 11 | post: ReactPropTypes.object.isRequired, 12 | context:React.PropTypes.object.isRequired 13 | }, 14 | render: function() { 15 | var post = this.props.post || {}; 16 | var date = post.date; 17 | var to = post.permalink; 18 | var tags = this.props.post.tags && ; 19 | return ( 20 |
21 |
22 |

{post.title}

23 |
24 |
25 |

» 26 |
27 | 28 |
29 | 30 | {post.author} 31 | {tags} 32 | 33 |
34 |
35 | ); 36 | } 37 | }); 38 | 39 | module.exports = PostListElement; 40 | -------------------------------------------------------------------------------- /shared/stores/ApplicationStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var createStore = require('fluxible/utils/createStore'); 4 | 5 | var ApplicationStore = createStore({ 6 | storeName: 'ApplicationStore', 7 | handlers: { 8 | 'CHANGE_ROUTE_SUCCESS': 'handleNavigate', 9 | 'SET_ERROR': 'setError', 10 | 'SET_APP_GLOBALS': 'setAppGlobals' 11 | }, 12 | initialize: function(dispatcher) { //jshint ignore:line 13 | this.route = {}; 14 | this.globals = {}; 15 | this.error = null; 16 | }, 17 | setAppGlobals: function(globals) { 18 | this.globals = globals; 19 | this.emitChange(); 20 | }, 21 | setError: function(error) { 22 | this.error = error; 23 | this.pageTitle = 'error'; 24 | this.emitChange(); 25 | }, 26 | handleNavigate: function(route) { 27 | if (route.url === this.route.url) { 28 | return; 29 | } 30 | this.route = route; 31 | this.emit('change'); 32 | }, 33 | getCurrentPageName: function() { 34 | return this.currentPageName; 35 | }, 36 | getState: function() { 37 | return { 38 | route: this.route, 39 | error: this.error, 40 | globals: this.globals 41 | }; 42 | }, 43 | dehydrate: function() { 44 | return this.getState(); 45 | }, 46 | rehydrate: function(state) { 47 | this.route = state.route; 48 | this.error = state.error; 49 | this.globals = state.globals; 50 | this.emitChange(); 51 | } 52 | }); 53 | 54 | module.exports = ApplicationStore; 55 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/modules/_buttons.scss: -------------------------------------------------------------------------------- 1 | .menu-button { 2 | -webkit-box-sizing: border-box; 3 | -moz-box-sizing: border-box; 4 | box-sizing: border-box; 5 | display: inline-block; 6 | float: left; 7 | height: 32px; 8 | padding: 0 20px; 9 | color: $white; 10 | text-align: center; 11 | @include font-size(16); 12 | text-transform: uppercase; 13 | line-height: 32px; 14 | white-space: nowrap; 15 | border-radius: 3px; 16 | transition: all ease 0.3s; 17 | margin-right: 10px; 18 | 19 | } 20 | .menu-button.inverted { 21 | color: $blue; 22 | } 23 | 24 | .menu-button.fright { 25 | float: right; 26 | } 27 | 28 | .back-button:before { 29 | position: relative; 30 | bottom: -2px; 31 | font-size: 13px; 32 | line-height: 0; 33 | margin-right: 8px; 34 | } 35 | 36 | /* Special styles when overlaid on an image*/ 37 | .main-nav.overlay { 38 | position: absolute; 39 | top: 0; 40 | left: 0; 41 | right: 0; 42 | height: 70px; 43 | border: none; 44 | background: linear-gradient(to bottom, rgba(0,0,0,0.2) 0%,rgba(0,0,0,0) 100%); 45 | } 46 | .no-cover .subscribe-button { 47 | float: right; 48 | } 49 | 50 | .menu-button:hover { 51 | color: $blue; 52 | border-color: $white; 53 | background: $white; 54 | transition: all 0.1s ease; 55 | } 56 | .menu-button.inverted:hover { 57 | color: $light-black; 58 | transition: all 0.1s ease; 59 | } 60 | 61 | .menu-button.icon-feed:before { 62 | font-size: rem-calc(10); 63 | margin-right: 6px; 64 | } 65 | -------------------------------------------------------------------------------- /shared/context.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var FluxibleApp = require('fluxible'); 5 | var routrPlugin = require('fluxible-plugin-routr'); 6 | var fetchrPlugin = require('fluxible-plugin-fetchr'); 7 | var routes = require('../client/client-routes'); 8 | var config = require('./config'); 9 | var appComponent = React.createFactory(require('../content/themes/' + config.theme + '/App')); 10 | 11 | var context = new FluxibleApp({ 12 | appComponent: appComponent 13 | }); 14 | 15 | context.plug(routrPlugin({ 16 | routes: routes 17 | })); 18 | 19 | context.plug(fetchrPlugin({ 20 | xhrPath: '/api' 21 | })); 22 | 23 | function configPlugin(options) { 24 | var config = options.config; 25 | 26 | return { 27 | name: 'configPlugin', 28 | plugContext: function() { 29 | return { 30 | plugActionContext: function(actionContext) { 31 | actionContext.config = config; 32 | }, 33 | dehydrate: function() { 34 | return { 35 | config: config 36 | }; 37 | }, 38 | rehydrate: function(state) { 39 | config = state.config; 40 | } 41 | }; 42 | } 43 | }; 44 | } 45 | 46 | context.plug(configPlugin({ 47 | config: config 48 | })); 49 | 50 | 51 | context.registerStore(require('./stores/ContentStore')); 52 | context.registerStore(require('./stores/ContentListStore')); 53 | context.registerStore(require('./stores/ApplicationStore')); 54 | context.registerStore(require('./stores/MetaStore')); 55 | 56 | module.exports = context; 57 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/modules/_icons.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Ionicons"; 3 | src: url("../fonts/ionicons.eot?v=1.5.2#iefix") format("embedded-opentype"), url("../fonts/ionicons.ttf?v=1.5.2") format("truetype"), url("../fonts/ionicons.woff?v=1.5.2") format("woff"), url("../fonts/ionicons.svg?v=1.5.2#Ionicons") format("svg"); 4 | font-weight: normal; 5 | font-style: normal; 6 | 7 | } 8 | /* Apply these base styles to all icons */ 9 | [class^="icon-"]:before, 10 | [class*=" icon-"]:before { 11 | font-family: "Ionicons", "Open Sans", sans-serif; 12 | speak: none; 13 | font-style: normal; 14 | font-weight: normal; 15 | font-variant: normal; 16 | text-transform: none; 17 | line-height: 1; 18 | text-decoration: none !important; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | 22 | } 23 | /* Each icon is created by inserting the correct character into the 24 | content of the :before pseudo element. Like a boss. */ 25 | .icon-morpheus:before { 26 | content: "\f15a"; 27 | } 28 | .icon-feed:before { 29 | content: "\f23d"; 30 | } 31 | .icon-twitter:before { 32 | content: "\f243"; 33 | font-size: 1.1em; 34 | } 35 | .icon-google-plus:before { 36 | content: "\f146"; 37 | } 38 | .icon-facebook:before { 39 | content: "\f231"; 40 | } 41 | .icon-arrow-left:before { 42 | content: "\f153"; 43 | } 44 | .icon-stats:before { 45 | content: "\f606"; 46 | } 47 | .icon-location:before { 48 | content: "\f607"; 49 | margin-left: -3px; 50 | /* Tracking fix */ 51 | } 52 | .icon-link:before { 53 | content: "\f608"; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /content/themes/blablabla/Disqus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var Disqus = React.createClass({ 6 | propTypes:{ 7 | shortName: React.PropTypes.string.isRequired, 8 | identifier: React.PropTypes.string.isRequired, 9 | title: React.PropTypes.string.isRequired, 10 | url: React.PropTypes.string.isRequired 11 | 12 | }, 13 | componentDidMount: function() { 14 | return this.addScript(); 15 | }, 16 | addScript: function() { 17 | window.disqus_shortname = this.props.shortName; //jshint ignore:line 18 | window.disqus_identifier = this.props.identifier;//jshint ignore:line 19 | window.disqus_title = this.props.title;//jshint ignore:line 20 | window.disqus_url = this.props.url;//jshint ignore:line 21 | if (window.DISQUS) { 22 | window.DISQUS.reset({ 23 | reload: true, 24 | config: function () { 25 | this.page.identifier = window.disqus_identifier; // jshint ignore:line 26 | this.page.url = window.disqus_url; // jshint ignore:line 27 | this.page.title = window.disqus_title; // jshint ignore:line 28 | } 29 | }); 30 | }else{ 31 | var dsq = document.createElement('script'); 32 | dsq.type = 'text/javascript'; 33 | dsq.async = true; 34 | dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; //jshint ignore:line 35 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); 36 | } 37 | }, 38 | render: function() { 39 | return
; 40 | } 41 | }); 42 | 43 | module.exports = Disqus; 44 | -------------------------------------------------------------------------------- /server/middlewares/not-found.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var serverUtils = require('../../server/utils'); 5 | var React = require('react'); 6 | var ContentActions = require('../../shared/actions/ContentActions'); 7 | var serialize = require('serialize-javascript'); 8 | 9 | module.exports = function(log) { 10 | return function(req, res) { // jshint ignore:line 11 | var message = util.format('page %s not found', req.protocol + '://' + req.get('host') + req.originalUrl); 12 | log.error('There was an error while handling the request, a page haven\'t been found: ' + message); 13 | res.status(404); 14 | res.format({ 15 | 'html': function() { 16 | var context = res.locals.context; 17 | var fluxibleApp = res.locals.fluxibleApp; 18 | context.getActionContext().executeAction(ContentActions.error, { 19 | err: { 20 | message: message, 21 | status: 404 22 | } 23 | }, function() { 24 | var AppComponent = fluxibleApp.getAppComponent(); 25 | var markup = React.renderToString(AppComponent({ //jshint ignore:line 26 | context: context.getComponentContext(), 27 | enableScroll: false 28 | })); 29 | res.locals.state = 'window.Morpheus=' + serialize(fluxibleApp.dehydrate(context)) + ';'; 30 | serverUtils.render(res, markup); 31 | }); 32 | }, 33 | 'json': function() { 34 | res.send({ 35 | error: message 36 | }); 37 | }, 38 | 'default': function() { 39 | res.send(); 40 | } 41 | }); 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | 4 | # Runtime data 5 | pids 6 | *.pid 7 | *.seed 8 | 9 | # Directory for instrumented libs generated by jscoverage/JSCover 10 | lib-cov 11 | 12 | # Coverage directory used by tools like istanbul 13 | coverage 14 | 15 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 16 | .grunt 17 | 18 | # Compiled binary addons (http://nodejs.org/api/addons.html) 19 | build/Release 20 | 21 | # Dependency directory 22 | # Commenting this out is preferred by some people, see 23 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 24 | node_modules 25 | 26 | # Users Environment Variables 27 | .lock-wscript 28 | 29 | ### Sass ### 30 | .sass-cache 31 | 32 | ### OSX ### 33 | .DS_Store 34 | .AppleDouble 35 | .LSOverride 36 | 37 | # Icon must end with two \r 38 | Icon 39 | 40 | # Thumbnails 41 | ._* 42 | 43 | # Files that might appear on external disk 44 | .Spotlight-V100 45 | .Trashes 46 | 47 | # Directories potentially created on remote AFP share 48 | .AppleDB 49 | .AppleDesktop 50 | Network Trash Folder 51 | Temporary Items 52 | .apdisk 53 | 54 | # Windows image file caches 55 | Thumbs.db 56 | ehthumbs.db 57 | 58 | # Folder config file 59 | Desktop.ini 60 | 61 | # Recycle Bin used on file shares 62 | $RECYCLE.BIN/ 63 | 64 | # Windows Installer files 65 | *.cab 66 | *.msi 67 | *.msm 68 | *.msp 69 | 70 | # Windows shortcuts 71 | *.lnk 72 | 73 | 74 | ## Directory-based project format: 75 | .idea/ 76 | 77 | #app specific 78 | content/themes/*/**/dist/*.json 79 | content/themes/*/**/dist/bundle.js 80 | content/themes/*/**/dist/main.css 81 | content/themes/*/**/dist/*.map 82 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/modules/_pagination.scss: -------------------------------------------------------------------------------- 1 | .pagination { 2 | position: relative; 3 | width: 80%; 4 | max-width: 710px; 5 | margin: 4rem auto; 6 | font-family: "Open Sans", sans-serif; 7 | font-size: rem-calc(13); 8 | color: #9eabb3; 9 | text-align: center; 10 | } 11 | .pagination a { 12 | color: #9eabb3; 13 | transition: all 0.2s ease; 14 | 15 | } 16 | /* Push the previous/next links out to the left/right */ 17 | .older-posts, 18 | .newer-posts { 19 | position: absolute; 20 | display: inline-block; 21 | padding: 0 5px; 22 | text-decoration: none; 23 | transition: border ease 0.3s; 24 | } 25 | .older-posts { 26 | right: 0; 27 | } 28 | .page-number { 29 | display: inline-block; 30 | padding: 2px 0; 31 | min-width: 100px; 32 | } 33 | .newer-posts { 34 | left: 0; 35 | } 36 | .older-posts:hover, 37 | .newer-posts:hover { 38 | color: #889093; 39 | border-color: #98a0a4; 40 | border-bottom: 1px solid; 41 | } 42 | .extra-pagination { 43 | display: none; 44 | border-bottom: #ebf2f6 1px solid; 45 | } 46 | .extra-pagination:after { 47 | display: block; 48 | content: ""; 49 | width: 7px; 50 | height: 7px; 51 | border: #e7eef2 1px solid; 52 | position: absolute; 53 | bottom: -5px; 54 | left: 50%; 55 | margin-left: -5px; 56 | background: #fff; 57 | -webkit-border-radius: 100%; 58 | -moz-border-radius: 100%; 59 | border-radius: 100%; 60 | box-shadow: #fff 0 0 0 5px; 61 | } 62 | .extra-pagination .pagination { 63 | width: auto; 64 | 65 | } 66 | /* On page2+ make all the headers smaller */ 67 | .archive-template .main-header { 68 | max-height: 30%; 69 | 70 | } 71 | /* On page2+ show extra pagination controls at the top of post list */ 72 | .archive-template .extra-pagination { 73 | display: block; 74 | 75 | } 76 | -------------------------------------------------------------------------------- /content/pages/a-static-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: page 3 | slug: a-static-page 4 | title: A static page 5 | description: This is a static page 6 | --- 7 | 8 | # Hello from a static page 9 | 10 | **this is a static page** 11 | 12 | ## Paragraph 1 13 | Vel vestibulum consectetur et consectetur adipiscing parturient ad vestibulum torquent nisl pretium inceptos torquent pulvinar scelerisque a natoque per lectus potenti id consectetur ad.Condimentum mus parturient a phasellus parturient nostra fermentum id cum nec parturient cursus lobortis ullamcorper mi mi condimentum ac habitant fringilla.Parturient vulputate rutrum ac penatibus dui rhoncus a a ante cum luctus eleifend condimentum sit curabitur parturient a tempor consectetur ullamcorper nisl.Tincidunt non montes adipiscing. 14 | 15 | ## Paragraph 2 16 | Vel vestibulum consectetur et consectetur adipiscing parturient ad vestibulum torquent nisl pretium inceptos torquent pulvinar scelerisque a natoque per lectus potenti id consectetur ad.Condimentum mus parturient a phasellus parturient nostra fermentum id cum nec parturient cursus lobortis ullamcorper mi mi condimentum ac habitant fringilla.Parturient vulputate rutrum ac penatibus dui rhoncus a a ante cum luctus eleifend condimentum sit curabitur parturient a tempor consectetur ullamcorper nisl.Tincidunt non montes adipiscing. 17 | 18 | ## Paragraph 3 19 | Vel vestibulum consectetur et consectetur adipiscing parturient ad vestibulum torquent nisl pretium inceptos torquent pulvinar scelerisque a natoque per lectus potenti id consectetur ad.Condimentum mus parturient a phasellus parturient nostra fermentum id cum nec parturient cursus lobortis ullamcorper mi mi condimentum ac habitant fringilla.Parturient vulputate rutrum ac penatibus dui rhoncus a a ante cum luctus eleifend condimentum sit curabitur parturient a tempor consectetur ullamcorper nisl.Tincidunt non montes adipiscing. 20 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowSpacesInNamedFunctionExpression": { 3 | "beforeOpeningRoundBrace": true 4 | }, 5 | "disallowSpacesInFunctionExpression": { 6 | "beforeOpeningRoundBrace": true 7 | }, 8 | "disallowSpacesInAnonymousFunctionExpression": { 9 | "beforeOpeningRoundBrace": true 10 | }, 11 | "disallowSpacesInFunctionDeclaration": { 12 | "beforeOpeningRoundBrace": true 13 | }, 14 | "disallowEmptyBlocks": true, 15 | "disallowSpacesInsideArrayBrackets": true, 16 | "disallowSpacesInsideParentheses": true, 17 | "disallowQuotedKeysInObjects": true, 18 | "disallowSpaceAfterObjectKeys": true, 19 | "disallowSpaceAfterPrefixUnaryOperators": true, 20 | "disallowSpaceBeforePostfixUnaryOperators": true, 21 | "disallowSpaceBeforeBinaryOperators": [ 22 | "," 23 | ], 24 | "disallowMixedSpacesAndTabs": true, 25 | "disallowTrailingWhitespace": true, 26 | "disallowTrailingComma": true, 27 | "disallowYodaConditions": true, 28 | "disallowKeywords": [ "with" ], 29 | "disallowMultipleLineBreaks": true, 30 | "requireSpaceBeforeBlockStatements": true, 31 | "requireParenthesesAroundIIFE": true, 32 | "requireSpacesInConditionalExpression": true, 33 | "requireMultipleVarDecl": "false", 34 | "requireBlocksOnNewline": 1, 35 | "requireCommaBeforeLineBreak": true, 36 | "requireSpaceBeforeBinaryOperators": true, 37 | "requireSpaceAfterBinaryOperators": true, 38 | "requireCamelCaseOrUpperCaseIdentifiers": true, 39 | "requireLineFeedAtFileEnd": true, 40 | "requireCapitalizedConstructors": true, 41 | "requireDotNotation": true, 42 | "requireSpacesInForStatement": true, 43 | "requireCurlyBraces": [ 44 | "do" 45 | ], 46 | "requireSpaceAfterKeywords": [ 47 | "if", 48 | "else", 49 | "for", 50 | "while", 51 | "do", 52 | "switch", 53 | "case", 54 | "return", 55 | "try", 56 | "catch", 57 | "typeof" 58 | ], 59 | "safeContextKeyword": "_this", 60 | "validateLineBreaks": "LF", 61 | "validateQuoteMarks": "'", 62 | "validateIndentation": 2 63 | } 64 | -------------------------------------------------------------------------------- /server/logger-factory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bunyan = require('bunyan'); 4 | var bformat = require('bunyan-format'); 5 | var formatOut = bformat({ 6 | outputMode: 'long' 7 | }); 8 | 9 | function Logger(bunyanLogger, config) { 10 | var self = this; 11 | this.bunyanLogger = bunyanLogger; 12 | this.config = config; 13 | 14 | ['fatal', 'error', 'warn', 'info', 'debug', 'trace']. 15 | forEach(function(level) { 16 | self[level] = function() { 17 | self.bunyanLogger[level].apply(self.bunyanLogger, arguments); 18 | }; 19 | }); 20 | } 21 | 22 | Logger.prototype.log = function(level) { 23 | this[level].apply(this, Array.prototype.slice.call(arguments, 1)); 24 | }; 25 | 26 | 27 | function LoggerFactory(config) { 28 | this.config = config; 29 | this.rootBunyanLogger = null; 30 | this.cache = {}; 31 | this.defaultConfig = { 32 | level: 'info', 33 | name: 'app', 34 | streams: [{ 35 | level: this.config.log.level || 'info', 36 | stream: formatOut 37 | }, { 38 | level: this.config.log.level || 'info', 39 | type: 'rotating-file', 40 | path: this.config.log.file, 41 | period: '1d', 42 | count: 3 43 | }], 44 | serializers: bunyan.stdSerializers 45 | }; 46 | this.initialize(); 47 | } 48 | 49 | LoggerFactory.prototype.initialize = function() { 50 | this.rootBunyanLogger = bunyan.createLogger(this.defaultConfig); 51 | this.cache.root = new Logger(this.rootBunyanLogger, this.config); 52 | }; 53 | 54 | LoggerFactory.prototype.create = function(name) { 55 | if (!name) { 56 | return this.cache.root; 57 | } 58 | var currentLogger = this.cache.root; 59 | if (!this.cache[name]) { 60 | this.cache[name] = new Logger(currentLogger.bunyanLogger.child({ 61 | component: name 62 | })); 63 | currentLogger = this.cache[name]; 64 | } 65 | return currentLogger; 66 | }; 67 | 68 | module.exports = LoggerFactory; 69 | -------------------------------------------------------------------------------- /server/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var serverUtils = require('../../server/utils'); 5 | var React = require('react'); 6 | var ContentActions = require('../../shared/actions/ContentActions'); 7 | var serialize = require('serialize-javascript'); 8 | 9 | module.exports = function(log) { 10 | return function(err, req, res, next) { // jshint ignore:line 11 | log.error({ 12 | err: err.stack 13 | }, 'There was an error while handling the request'); 14 | if (!err.statusCode) { 15 | err.statusCode = err.status ? err.status : 500; 16 | } 17 | if (err.statusCode === 404) { 18 | err.message = util.format('page %s not found', req.protocol + '://' + req.get('host') + req.originalUrl); 19 | } 20 | if (err.statusCode === 500) { 21 | err.message = err.message ? 'Internal Server Error - ' + err.message : 'Internal Server Error'; 22 | } 23 | res.status(err.statusCode); 24 | res.format({ 25 | 'html': function() { 26 | var context = res.locals.context; 27 | var fluxibleApp = res.locals.fluxibleApp; 28 | context.getActionContext().executeAction(ContentActions.error, { 29 | err: { 30 | message: err.message, 31 | status: err.statusCode 32 | } 33 | }, function() { 34 | var AppComponent = fluxibleApp.getAppComponent(); 35 | var markup = React.renderToString(AppComponent({ //jshint ignore:line 36 | context: context.getComponentContext(), 37 | enableScroll: false 38 | })); 39 | res.locals.state = 'window.Morpheus=' + serialize(fluxibleApp.dehydrate(context)) + ';'; 40 | serverUtils.render(res, markup); 41 | }); 42 | }, 43 | 'json': function() { 44 | res.send({ 45 | error: err.message || 'Unexpected error' 46 | }); 47 | }, 48 | 'default': function() { 49 | res.send(); 50 | } 51 | }); 52 | }; 53 | }; 54 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### What Can I Do? 2 | 3 | - **Contribute code.** See below for instructions on Submitting a Pull Request. 4 | - **Participate in discussion.** 5 | - **Spread the word.** Know someone who could help out? Please share this project with them! 6 | 7 | #### Submitting a Pull Request 8 | 9 | Good pull requests - patches, improvements, new features - are a fantastic 10 | help. They should remain focused in scope and avoid containing unrelated 11 | commits. 12 | 13 | 14 | 1. [Fork](https://help.github.com/articles/fork-a-repo) the project, clone your fork, and configure the remotes: 15 | 16 | ```bash 17 | # Clone your fork of the repo into the current directory 18 | git clone https://github.com//morpheus.git 19 | # Navigate to the newly cloned directory 20 | cd morpheus 21 | # Assign the original repo to a remote called "upstream" 22 | git remote add upstream https://github.com/vesparny/morpheus.git 23 | ``` 24 | 25 | 2. Create a new topic branch (off the develop branch) to contain your feature, change, or fix: 26 | 27 | ```bash 28 | git checkout develop 29 | git checkout -b 30 | ``` 31 | 32 | 3. Commit your changes in logical chunks. Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 33 | 34 | 4. When you are ready, clean up. Squash together minor commits. Use Git's [interactive rebase](https://help.github.com/articles/about-git-rebase) feature to tidy up your commits before making them public. 35 | 36 | ```bash 37 | git rebase -i 38 | ``` 39 | 40 | 5. Rebase the current topic branch onto upstream/develop to get the last changes (needed if it takes you a while to finish your changes). 41 | 42 | ```bash 43 | git fetch upstream 44 | git rebase upstream/develop 45 | ``` 46 | You might have to fix merge conflicts. git status will show you the unmerged files. Resolve all the conflicts, then continue the rebase: 47 | 48 | ```bash 49 | git add ... # add resolved files 50 | git rebase --continue 51 | ``` 52 | 53 | 6. Check that all tests still pass and push your branch remotely: 54 | 55 | ```bash 56 | git push origin 57 | ``` 58 | 59 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title and description. 60 | -------------------------------------------------------------------------------- /content/themes/blablabla/Html.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ApplicationStore = require('../../../shared/stores/ApplicationStore'); 5 | var MetaStore = require('../../../shared/stores/MetaStore'); 6 | 7 | var Html = React.createClass({ 8 | 9 | render: function(){ 10 | var siteGlobals = this.props.context.getStore(ApplicationStore).getState().globals; 11 | var siteMeta = this.props.context.getStore(MetaStore).getState().meta; 12 | 13 | var siteUrl = siteGlobals.siteUrl; 14 | var title = siteMeta.title || siteGlobals.siteTitle; 15 | var description = siteMeta.description || siteGlobals.siteDescription; 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | {title} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ) 49 | } 50 | }); 51 | 52 | module.exports = Html; 53 | -------------------------------------------------------------------------------- /branding/morpheus logo square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | morpheus logo square 4 | Created with Sketch (http://www.bohemiancoding.com/sketch) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /branding/morpheus 16x16.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | morpheus 16x16 4 | Created with Sketch (http://www.bohemiancoding.com/sketch) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /shared/actions/ContentActions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assign = require('object-assign'); 4 | 5 | function buildAppGlobalsPayload(config) { 6 | var debug = require('debug'); 7 | if (config.debug) { 8 | debug.enable('*'); 9 | } else { 10 | debug.disable(); 11 | } 12 | return { 13 | siteTitle: config.siteTitle, 14 | siteUrl: config.siteUrl, 15 | siteDescription: config.siteDescription, 16 | authors: config.authors, 17 | googleAnalytics: config.googleAnalytics, 18 | disqusComments: config.disqusComments, 19 | version: config.version 20 | }; 21 | } 22 | 23 | module.exports = { 24 | list: function(context, payload, done) { 25 | context.dispatch('RESET_CONTENT_LIST'); 26 | context.dispatch('SET_APP_GLOBALS', buildAppGlobalsPayload(context.config)); 27 | context.service.read('content', { 28 | actionType: 'list', 29 | page: payload.page || '1' 30 | }, {}, function(err, result) { 31 | if (err) { 32 | context.dispatch('GET_CONTENT_LIST_FAILURE'); 33 | return done(err); 34 | } 35 | context.dispatch('GET_CONTENT_LIST_SUCCESS', result.data); 36 | context.dispatch('SET_META', { 37 | description: context.config.siteDescription, 38 | title: context.config.siteTitle + (payload.page && payload.page !== '1' ? ' - Page ' + payload.page : ''), 39 | pagination: result.meta 40 | }); 41 | done(); 42 | }); 43 | }, 44 | single: function(context, payload, done) { 45 | context.dispatch('RESET_CONTENT'); 46 | context.dispatch('RESET_CONTENT_LIST'); 47 | context.dispatch('SET_APP_GLOBALS', buildAppGlobalsPayload(context.config)); 48 | context.service.read('content', { 49 | slug: payload.slug, 50 | actionType: 'single' 51 | }, {}, function(err, result) { 52 | if (err) { 53 | context.dispatch('GET_CONTENT_FAILURE'); 54 | return done(err); 55 | } 56 | context.dispatch('GET_CONTENT_SUCCESS', result.data); 57 | context.dispatch('SET_META', assign(result.data, { 58 | pagination: result.meta 59 | })); 60 | done(); 61 | }); 62 | }, 63 | /* 64 | tag: function(context, payload, done) { 65 | context.service.read('content', { 66 | tag: payload.tag, 67 | actionType: 'tag' 68 | }, {}, function(err, single) { 69 | context.dispatch('GET_CONTENT_SUCCESS', single); 70 | context.dispatch('UPDATE_PAGE_TITLE', single.title); 71 | done(); 72 | }); 73 | },*/ 74 | error: function(context, payload, done) { 75 | context.dispatch('SET_ERROR', payload.err); 76 | done(); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "morpheus", 3 | "version": "0.0.1-alpha1", 4 | "description": "The next generation web publishing platform.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/vesparny/morpheus" 8 | }, 9 | "keywords": [ 10 | "react", 11 | "browserify", 12 | "blog", 13 | "isomorphic" 14 | ], 15 | "author": "Alessandro Arnodo", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/vesparny/morpheus/issues" 19 | }, 20 | "homepage": "https://github.com/vesparny/morpheus", 21 | "scripts": { 22 | "start": "node server.js", 23 | "test": "./node_modules/.bin/gulp test" 24 | }, 25 | "main": "server.js", 26 | "dependencies": { 27 | "body-parser": "~1.11.0", 28 | "bunyan": "~1.3.3", 29 | "bunyan-format": "~0.2.1", 30 | "cheerio": "~0.18.0", 31 | "connect-slashes": "~1.3.0", 32 | "debug": "~2.1.1", 33 | "downsize": "~0.0.8", 34 | "envify": "~3.2.0", 35 | "es6-promise": "~2.0.1", 36 | "express": "~4.11.2", 37 | "flux-router-component": "~0.5.3", 38 | "fluxible": "~0.1.5", 39 | "fluxible-plugin-fetchr": "~0.2.1", 40 | "fluxible-plugin-routr": "~0.3.0", 41 | "front-matter": "~0.2.1", 42 | "glob": "~4.3.5", 43 | "gravatar": "~1.1.0", 44 | "helmet": "~0.6.0", 45 | "inherits": "~2.0.1", 46 | "marked": "~0.3.3", 47 | "method-override": "~2.3.1", 48 | "moment": "~2.9.0", 49 | "multer": "~0.1.7", 50 | "node-jsx": "~0.12.4", 51 | "object-assign": "~2.0.0", 52 | "react": "~0.12.2", 53 | "rss": "~1.1.1", 54 | "serialize-javascript": "~1.0.0", 55 | "serve-favicon": "~2.2.0", 56 | "underscore.string": "~3.0.2", 57 | "validator": "~3.28.0" 58 | }, 59 | "devDependencies": { 60 | "6to5-core": "^3.3.9", 61 | "6to5-loader": "^3.0.0", 62 | "del": "~1.1.1", 63 | "gulp": "~3.8.10", 64 | "gulp-changed": "~1.1.0", 65 | "gulp-cssmin": "~0.1.6", 66 | "gulp-if": "~1.2.5", 67 | "gulp-load-plugins": "~0.8.0", 68 | "gulp-mocha": "~2.0.0", 69 | "gulp-nodemon": "~1.0.5", 70 | "gulp-notify": "~2.2.0", 71 | "gulp-rename": "~1.2.0", 72 | "gulp-replace": "~0.5.2", 73 | "gulp-rev": "~3.0.0", 74 | "gulp-ruby-sass": "~0.7.1", 75 | "gulp-uglify": "~1.1.0", 76 | "gulp-util": "~3.0.3", 77 | "gulp-webpack": "^1.2.0", 78 | "jest-cli": "^0.2.2", 79 | "json-loader": "^0.5.1", 80 | "minimist": "~1.1.0", 81 | "mocha": "~2.1.0", 82 | "run-sequence": "~1.0.2", 83 | "should": "~4.6.3", 84 | "sinon": "~1.12.2", 85 | "supertest": "~0.15.0", 86 | "webpack": "^1.5.3" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /server/services/rss.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RSS = require('rss'); 4 | var Promise = require('es6-promise').Promise; // jshint ignore:line 5 | var config = require('../../shared/config'); 6 | var path = require('path'); 7 | var repositories = require(path.resolve(config.appRoot, 'server', config.repositoryStrategy.type))(); 8 | var serverUtils = require('../../server/utils'); 9 | var cheerio = require('cheerio'); 10 | var url = require('url'); 11 | 12 | var rssService = { 13 | getFeed: function() { 14 | var feed = new RSS({ 15 | title: config.siteTitle, 16 | description: config.siteDescription, 17 | generator: 'Morpheus ' + config.version, 18 | feed_url: config.siteUrl + '/rss/', // jshint ignore:line 19 | site_url: config.siteUrl, // jshint ignore:line 20 | ttl: '60' 21 | }); 22 | return new Promise(function(resolve, reject) { 23 | repositories.content.getPostsForFeed({ 24 | contentPath: config.contentPath, 25 | permalinkStructure: config.permalinkStructure 26 | }).then(function(posts) { 27 | var promiseArray = []; 28 | var itemsArray = []; 29 | posts.forEach(function(post){ 30 | var item = { 31 | title: post.title, 32 | guid: post.slug, 33 | url: config.siteUrl + post.permalink, 34 | date: post.rawDate.format(), 35 | categories: post.tags, 36 | author: post.author, 37 | }; 38 | itemsArray.push(item); 39 | promiseArray.push(serverUtils.md2html(post.body)); 40 | }); 41 | Promise.all(promiseArray).then(function(data) { 42 | data.forEach(function(content, index){ 43 | var currentItem = itemsArray[index]; 44 | var htmlContent = cheerio.load(content, {decodeEntities: false}); 45 | // convert relative resource urls to absolute 46 | ['href', 'src'].forEach(function (attributeName) { 47 | htmlContent('[' + attributeName + ']').each(function (index, el) { 48 | el = htmlContent(el); 49 | var attributeValue = el.attr(attributeName); 50 | attributeValue = url.resolve(config.siteUrl, attributeValue); 51 | el.attr(attributeName, attributeValue); 52 | }); 53 | }); 54 | currentItem.description = htmlContent.html(); 55 | feed.item(currentItem); 56 | }); 57 | resolve(feed); 58 | }).catch(function(err){ 59 | reject(err); 60 | }); 61 | }).catch(function(err){ 62 | reject(err); 63 | }); 64 | }); 65 | } 66 | }; 67 | 68 | module.exports = rssService; 69 | -------------------------------------------------------------------------------- /server/express-loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | var express = require('express'); 5 | var slashes = require('connect-slashes'); 6 | var path = require('path'); 7 | var bodyParser = require('body-parser'); 8 | var favicon = require('serve-favicon'); 9 | var methodOverride = require('method-override'); 10 | var multer = require('multer'); 11 | var routes = require('./routes'); 12 | var errors = require('./middlewares/errors'); 13 | var sslRedirection = require('./middlewares/ssl-redirection'); 14 | var sanitize = require('./middlewares/sanitize'); 15 | var requestLogger = require('./middlewares/request-logger'); 16 | var fluxibleContext = require('./middlewares/fluxible-context'); 17 | var robotstxt = require('./middlewares/robots'); 18 | var navigation = require('./middlewares/navigation'); 19 | var notFound = require('./middlewares/not-found'); 20 | var helmet = require('helmet'); 21 | var appContext = require('../shared/context'); 22 | var morpheus = require('../morpheus'); 23 | var server = express(); 24 | var fetchrPlugin = appContext.getPlugin('FetchrPlugin'); 25 | 26 | morpheus.logger.info('creating express application'); 27 | server.enable('strict routing'); 28 | server.use(requestLogger.call(null, morpheus.getLogger('express-loader'))); 29 | server.use(robotstxt); 30 | server.use(methodOverride()); 31 | server.use(bodyParser.json()); 32 | server.use(bodyParser.urlencoded({ 33 | extended: true 34 | })); 35 | server.use(multer()); 36 | server.use('/content/images', express.static(path.join(morpheus.config.appRoot, '/content/images/'))); 37 | server.use(favicon(path.join(morpheus.config.appRoot, 'content/favicon.ico'))); 38 | server.use(express.static(path.join(morpheus.config.appRoot, '/content/themes/', morpheus.config.theme))); 39 | 40 | // Use helmet to secure Express headers 41 | server.use(helmet.xframe()); 42 | server.use(helmet.xssFilter()); 43 | server.use(helmet.nosniff()); 44 | server.use(helmet.ienoopen()); 45 | 46 | // powered by Morpheus 47 | server.use(helmet.hidePoweredBy()); 48 | 49 | server.use(sslRedirection); 50 | 51 | server.use(fluxibleContext.call(null, appContext)); 52 | 53 | fetchrPlugin.registerService(morpheus.services.content); 54 | server.use(fetchrPlugin.getXhrPath(), fetchrPlugin.getMiddleware()); 55 | 56 | server.use(sanitize); 57 | 58 | server.use(slashes()); 59 | 60 | server.use(sslRedirection); 61 | 62 | server.use(navigation); 63 | 64 | routes(server); 65 | 66 | server.use(errors.call(null, morpheus.getLogger('express-loader'))); 67 | // Assume 404 since no middleware responded 68 | server.use(notFound.call(null, morpheus.getLogger('express-loader'))); 69 | return server; 70 | }; 71 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ContentActions = require('../../shared/actions/ContentActions'); 5 | var serverUtils = require('../../server/utils'); 6 | var validator = require('validator'); 7 | var config = require('../../shared/config'); 8 | var rssService = require('../services/rss'); 9 | var serialize = require('serialize-javascript'); 10 | 11 | module.exports = function(server) { 12 | 13 | function makeCall(req, res, next, options) { 14 | var context = res.locals.context; 15 | var fluxibleApp = res.locals.fluxibleApp; 16 | context.getActionContext().executeAction(options.action, options.params, function(err) { 17 | if (err) { 18 | return next(err); 19 | } 20 | res.locals.state = 'window.Morpheus=' + serialize(fluxibleApp.dehydrate(context)) + ';'; 21 | var AppComponent = fluxibleApp.getAppComponent(); 22 | var markup = React.renderToString(AppComponent({ //jshint ignore:line 23 | context: context.getComponentContext(), 24 | enableScroll: false 25 | })); 26 | serverUtils.render(res, markup); 27 | }); 28 | } 29 | 30 | server.get('/', function(req, res, next) { 31 | makeCall(req, res, next, { 32 | action: ContentActions.list, 33 | params: { 34 | page: '1' 35 | } 36 | }); 37 | }); 38 | 39 | server.get('/rss/', function(req, res, next) { 40 | rssService.getFeed().then(function(feed) { 41 | res.set('Content-Type', 'text/xml; charset=UTF-8'); 42 | res.send(feed.xml()); 43 | }).catch(function(err) { 44 | return next(err); 45 | }); 46 | }); 47 | 48 | server.get('/page/:page/', function(req, res, next) { 49 | var page = req.params.page || '0'; 50 | if (!validator.isInt(page)) { 51 | return next(); 52 | } 53 | makeCall(req, res, next, { 54 | action: ContentActions.list, 55 | params: { 56 | page: page 57 | } 58 | }); 59 | }); 60 | 61 | server.get('/:title/', function(req, res, next) { 62 | makeCall(req, res, next, { 63 | action: ContentActions.single, 64 | params: { 65 | slug: req.params.title 66 | } 67 | }); 68 | }); 69 | 70 | server.get('/tag/:tag/', function(req, res, next) { 71 | makeCall(req, res, next, { 72 | action: ContentActions.tag, 73 | params: { 74 | slug: req.params.title 75 | } 76 | }); 77 | }); 78 | 79 | if (config.permalinkStructure !== '/:title/') { 80 | server.get(config.permalinkStructure, function(req, res, next) { 81 | makeCall(req, res, next, { 82 | action: ContentActions.single, 83 | params: { 84 | slug: req.params.title 85 | } 86 | }); 87 | }); 88 | 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /content/themes/blablabla/PostList.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var PostListElement = require('./PostListElement'); 5 | var ContentActions = require('../../../shared/actions/ContentActions'); 6 | var cx = require('react/lib/cx'); 7 | var ContentListStore = require('../../../shared/stores/ContentListStore'); 8 | var StoreMixin = require('fluxible').StoreMixin; 9 | var Loader = require('./Loader'); 10 | var Header = require('./Header'); 11 | var Footer = require('./Footer'); 12 | var Paginator = require('./Paginator'); 13 | 14 | 15 | var PostList = React.createClass({ 16 | mixins: [StoreMixin], 17 | statics: { 18 | storeListeners: { 19 | _onChange: [ContentListStore] 20 | } 21 | }, 22 | propTypes:{ 23 | context:React.PropTypes.object.isRequired 24 | }, 25 | getInitialState: function() { 26 | return this.getStateFromStores(); 27 | }, 28 | getStateFromStores: function () { 29 | return { 30 | posts: this.getStore(ContentListStore).getState().contentList, 31 | cssClass: 'home' 32 | }; 33 | }, 34 | getDefaultProps: function() { 35 | return { 36 | page: '1' 37 | }; 38 | }, 39 | componentDidMount: function() { 40 | if(!this.state.posts){ 41 | this.props.context.executeAction(ContentActions.list); 42 | } 43 | }, 44 | componentWillReceiveProps: function(props){ 45 | if (props.page !== this.props.page) { 46 | this.props.context.executeAction(ContentActions.list, { 47 | page:props.page 48 | }); 49 | } 50 | }, 51 | _onChange: function() { 52 | this.setState(this.getStateFromStores()); 53 | }, 54 | render: function() { 55 | var posts = []; 56 | var showLoader = this.state.posts === null; 57 | var paginator = null; 58 | if (this.state.posts && this.state.posts.length > 0) { 59 | this.state.posts.forEach(function(post, index){ 60 | posts.push(); 61 | }.bind(this)); 62 | paginator = ; 63 | } 64 | var classesMap = {}; 65 | classesMap.wrapper = true; 66 | classesMap[this.state.cssClass] = true; 67 | var classes = cx(classesMap); 68 | return ( 69 |
70 |
71 |
72 |
73 | 74 | {posts} 75 | {paginator} 76 |
77 |
78 |
79 |
80 | ); 81 | } 82 | }); 83 | 84 | module.exports = PostList; 85 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/_animations.scss: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes fadeInDown { 2 | 0% { 3 | opacity: 0; 4 | -webkit-transform: translate3d(0, -100%, 0); 5 | transform: translate3d(0, -100%, 0); 6 | 7 | } 8 | 100% { 9 | opacity: 1; 10 | -webkit-transform: none; 11 | transform: none; 12 | } 13 | 14 | } 15 | @keyframes fadeInDown { 16 | 0% { 17 | opacity: 0; 18 | -webkit-transform: translate3d(0, -100%, 0); 19 | -ms-transform: translate3d(0, -100%, 0); 20 | transform: translate3d(0, -100%, 0); 21 | 22 | } 23 | 100% { 24 | opacity: 1; 25 | -webkit-transform: none; 26 | -ms-transform: none; 27 | transform: none; 28 | } 29 | 30 | } 31 | @-webkit-keyframes rubberBand { 32 | 0% { 33 | -webkit-transform: scale3d(1, 1, 1); 34 | transform: scale3d(1, 1, 1); 35 | 36 | } 37 | 30% { 38 | -webkit-transform: scale3d(1.25, 0.75, 1); 39 | transform: scale3d(1.25, 0.75, 1); 40 | 41 | } 42 | 40% { 43 | -webkit-transform: scale3d(0.75, 1.25, 1); 44 | transform: scale3d(0.75, 1.25, 1); 45 | 46 | } 47 | 50% { 48 | -webkit-transform: scale3d(1.15, 0.85, 1); 49 | transform: scale3d(1.15, 0.85, 1); 50 | 51 | } 52 | 65% { 53 | -webkit-transform: scale3d(.95, 1.05, 1); 54 | transform: scale3d(.95, 1.05, 1); 55 | 56 | } 57 | 75% { 58 | -webkit-transform: scale3d(1.05, .95, 1); 59 | transform: scale3d(1.05, .95, 1); 60 | 61 | } 62 | 100% { 63 | -webkit-transform: scale3d(1, 1, 1); 64 | transform: scale3d(1, 1, 1); 65 | } 66 | 67 | } 68 | @keyframes rubberBand { 69 | 0% { 70 | -webkit-transform: scale3d(1, 1, 1); 71 | -ms-transform: scale3d(1, 1, 1); 72 | transform: scale3d(1, 1, 1); 73 | 74 | } 75 | 30% { 76 | -webkit-transform: scale3d(1.25, 0.75, 1); 77 | -ms-transform: scale3d(1.25, 0.75, 1); 78 | transform: scale3d(1.25, 0.75, 1); 79 | 80 | } 81 | 40% { 82 | -webkit-transform: scale3d(0.75, 1.25, 1); 83 | -ms-transform: scale3d(0.75, 1.25, 1); 84 | transform: scale3d(0.75, 1.25, 1); 85 | 86 | } 87 | 50% { 88 | -webkit-transform: scale3d(1.15, 0.85, 1); 89 | -ms-transform: scale3d(1.15, 0.85, 1); 90 | transform: scale3d(1.15, 0.85, 1); 91 | 92 | } 93 | 65% { 94 | -webkit-transform: scale3d(.95, 1.05, 1); 95 | -ms-transform: scale3d(.95, 1.05, 1); 96 | transform: scale3d(.95, 1.05, 1); 97 | 98 | } 99 | 75% { 100 | -webkit-transform: scale3d(1.05, .95, 1); 101 | -ms-transform: scale3d(1.05, .95, 1); 102 | transform: scale3d(1.05, .95, 1); 103 | 104 | } 105 | 100% { 106 | -webkit-transform: scale3d(1, 1, 1); 107 | -ms-transform: scale3d(1, 1, 1); 108 | transform: scale3d(1, 1, 1); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /content/drafts/welcome-to-morpheus.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: post 3 | author: Your Name 4 | email: youreamail@yourwebsite.something 5 | tags: general 6 | slug: welcome-to-morpheus 7 | title: Welcome to morpheus 8 | description: This is an example post 9 | --- 10 | You're live! Nice. We've put together a little post to introduce you to Morpheus and Markdown. 11 | You can manage your content by creating posts and pages inside the `/content/` .folder 12 | 13 | More details about options, configurations and so on, are available in the [README file](https://github.com/vesparny/morpheus#readme). 14 | 15 | *Note: Feel free to play with this page.* 16 | 17 | ## Basic formatting 18 | 19 | Paragraphs can be written like so. A paragraph is the basic block of Markdown. A paragraph is what text will turn into when there is no reason it should become anything else. 20 | 21 | Paragraphs must be separated by a blank line. Basic formatting of *italics* and **bold** is supported. This *can be **nested** like* so. 22 | 23 | ## Lists 24 | 25 | ### Ordered list 26 | 27 | 1. Item 1 28 | 2. A second item 29 | 3. Number 3 30 | 4. Ⅳ 31 | 32 | ### Unordered list 33 | 34 | * An item 35 | * Another item 36 | * Yet another item 37 | * And there's more... 38 | 39 | ## Paragraph modifiers 40 | 41 | ### Code block 42 | 43 | ```javascript 44 | var hello = 'hello Morpheus!'; 45 | console.log(hello); 46 | ``` 47 | You can also make `inline code` to add code into other things. 48 | 49 | ### Quote 50 | 51 | > Here is a quote. What this is should be self explanatory. Quotes are automatically indented when they are used. 52 | 53 | ## Headings 54 | 55 | There are six levels of headings. They correspond with the six levels of HTML headings. You've probably noticed them already in the page. Each level down uses one more hash character. 56 | 57 | ### Headings *can* also contain **formatting** 58 | 59 | ### They can even contain `inline code` 60 | 61 | ## URLs 62 | 63 | URLs can be made in a handful of ways: 64 | 65 | * Another named link to [Morpheus](https://github.com/vesparny/morpheus/) 66 | * Sometimes you just want a URL like https://github.com/vesparny/morpheus/. 67 | 68 | ## Horizontal rule 69 | 70 | A horizontal rule is a line that goes across the middle of the page. 71 | 72 | --- 73 | 74 | It's sometimes handy for breaking things up. 75 | 76 | ## Images 77 | 78 | Markdown can also contain images. You can organize images as you want inside the `/content/images/` folder 79 | ![Morpheus](/content/images/posts/home.png) 80 | 81 | ## HTML 82 | You can write plain old HTML and it'll still work! Very flexible. 83 | 84 | 85 | 86 | and embed whatever you want! 87 | 88 |
89 | 90 |
91 | 92 | Have fun :) 93 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/layouts/_author.scss: -------------------------------------------------------------------------------- 1 | .post-head.main-header { 2 | height: 65%; 3 | min-height: 180px; 4 | } 5 | .no-cover.post-head.main-header { 6 | height: 85px; 7 | min-height: 0; 8 | margin-bottom: 0; 9 | background: transparent; 10 | } 11 | .tag-head.main-header { 12 | height: 40%; 13 | min-height: 180px; 14 | } 15 | .author-head.main-header { 16 | height: 40%; 17 | min-height: 180px; 18 | } 19 | .no-cover.author-head.main-header { 20 | height: 10%; 21 | min-height: 100px; 22 | background: transparent; 23 | } 24 | .author-profile { 25 | padding: 0 15px 5rem; 26 | border-bottom: #ebf2f6 1px solid; 27 | text-align: center; 28 | 29 | } 30 | /* Add a little circle in the middle of the border-bottom */ 31 | .author-profile:after { 32 | display: block; 33 | content: ""; 34 | width: 7px; 35 | height: 7px; 36 | border: #e7eef2 1px solid; 37 | position: absolute; 38 | bottom: -5px; 39 | left: 50%; 40 | margin-left: -5px; 41 | background: #fff; 42 | -webkit-border-radius: 100%; 43 | -moz-border-radius: 100%; 44 | border-radius: 100%; 45 | box-shadow: #fff 0 0 0 5px; 46 | } 47 | .author-image { 48 | -webkit-box-sizing: border-box; 49 | -moz-box-sizing: border-box; 50 | box-sizing: border-box; 51 | display: block; 52 | position: absolute; 53 | top: -55px; 54 | left: 50%; 55 | margin-left: -40px; 56 | width: 80px; 57 | height: 80px; 58 | border-radius: 100%; 59 | overflow: hidden; 60 | padding: 6px; 61 | background: #fff; 62 | z-index: 2; 63 | box-shadow: #e7eef2 0 0 0 1px; 64 | } 65 | .author-image .img { 66 | position: relative; 67 | display: block; 68 | width: 100%; 69 | height: 100%; 70 | background-size: cover; 71 | background-position: center center; 72 | border-radius: 100%; 73 | } 74 | .author-profile .author-image { 75 | position: relative; 76 | left: auto; 77 | top: auto; 78 | width: 120px; 79 | height: 120px; 80 | padding: 3px; 81 | margin: -100px auto 0; 82 | box-shadow: none; 83 | } 84 | .author-title { 85 | margin: 1.5rem 0 1rem; 86 | } 87 | .author-bio { 88 | @include font-size(18); 89 | line-height: 1.5em; 90 | font-weight: 200; 91 | color: #50585d; 92 | letter-spacing: 0; 93 | text-indent: 0; 94 | } 95 | .author-meta { 96 | margin: 1.6rem 0; 97 | 98 | } 99 | /* Location, website, and link */ 100 | .author-profile .author-meta { 101 | margin: 2rem 0; 102 | font-family: "Merriweather", serif; 103 | letter-spacing: 0.01rem; 104 | @include font-size(17); 105 | } 106 | .author-meta span { 107 | display: inline-block; 108 | margin: 0 2rem 1rem 0; 109 | word-wrap: break-word; 110 | } 111 | .author-meta a { 112 | text-decoration: none; 113 | 114 | } 115 | /* Turn off meta for page2+ to make room for extra 116 | pagination prev/next links */ 117 | .archive-template .author-profile .author-meta { 118 | display: none; 119 | 120 | } 121 | -------------------------------------------------------------------------------- /server/services/content.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('es6-promise').Promise; //jshint ignore:line 4 | var _s = require('underscore.string'); 5 | var serverUtils = require('../../server/utils'); 6 | var assign = require('object-assign'); 7 | var path = require('path'); 8 | var Promise = require('es6-promise').Promise; // jshint ignore:line 9 | 10 | function buildContent(item, siteUrl) { 11 | return new Promise(function(resolve, reject) { 12 | var tags = []; 13 | item.tags.forEach(function(tag) { 14 | tags.push({ 15 | path: _s.slugify(tag), 16 | name: tag 17 | }); 18 | }); 19 | item.tags = tags; 20 | item.fullUrl = siteUrl + item.permalink; 21 | serverUtils.md2html(item.body).then(function(content){ 22 | item.excerpt = serverUtils.excerpt(content); 23 | serverUtils.md2html(item.body).then(function(bodyContent){ 24 | item.content = bodyContent; 25 | delete item.body; 26 | resolve(item); 27 | }); 28 | }).catch(function(err){ 29 | reject(err); 30 | }); 31 | }); 32 | } 33 | 34 | var actions = { 35 | single: function(repository, params, config, callback) { 36 | repository.findOne({ 37 | slug: params.slug, 38 | siteUrl: config.siteUrl, 39 | contentPath: config.contentPath, 40 | permalinkStructure: config.permalinkStructure 41 | }).then(function(result) { 42 | buildContent(result.content, config.siteUrl).then(function(content){ 43 | delete result.content; 44 | result.data = content; 45 | callback(null, result); 46 | }).catch(function(err){ 47 | callback(err); 48 | }); 49 | }).catch(function(err) { 50 | callback(err); 51 | }); 52 | }, 53 | list: function(repository, params, config, callback) { 54 | repository.find(assign(params, { 55 | postPerPage: config.postPerPage, 56 | contentPath: config.contentPath, 57 | permalinkStructure: config.permalinkStructure 58 | })).then(function(result) { 59 | var promiseArray = []; 60 | result.content.forEach(function(el) { 61 | promiseArray.push(buildContent(el, config.siteUrl)); 62 | }); 63 | Promise.all(promiseArray).then(function(data) { 64 | delete result.content; 65 | result.data = data; 66 | callback(null, result); 67 | }).catch(function(err){ 68 | callback(err); 69 | }); 70 | }).catch(function(err) { 71 | callback(err); 72 | }); 73 | }, 74 | tag: function(repository, params, config, callback) { 75 | repository.findTag({ 76 | tag: params.tag 77 | }).then(function(data) { 78 | callback(null, data); 79 | }).catch(function(err) { 80 | callback(err); 81 | }); 82 | } 83 | }; 84 | 85 | var contentService = function(config) { 86 | var repositories = require(path.resolve(config.appRoot, 'server', config.repositoryStrategy.type))(); 87 | return { 88 | name: 'content', 89 | read: function(req, resource, params, conf, callback) { 90 | actions[params.actionType](repositories.content, params || {}, config, callback); 91 | } 92 | }; 93 | }; 94 | 95 | module.exports = contentService; 96 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/modules/_highlight.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Railscasts-like style (c) Visoft, Inc. (Damien White) 4 | 5 | */ 6 | 7 | pre code { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #232323; 12 | color: #e6e1dc; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .hljs-template_comment, 18 | .hljs-javadoc, 19 | .hljs-shebang { 20 | color: #bc9458; 21 | font-style: italic; 22 | } 23 | 24 | .hljs-keyword, 25 | .ruby .hljs-function .hljs-keyword, 26 | .hljs-request, 27 | .hljs-status, 28 | .nginx .hljs-title, 29 | .method, 30 | .hljs-list .hljs-title { 31 | color: #c26230; 32 | } 33 | 34 | .hljs-string, 35 | .hljs-number, 36 | .hljs-regexp, 37 | .hljs-tag .hljs-value, 38 | .hljs-cdata, 39 | .hljs-filter .hljs-argument, 40 | .hljs-attr_selector, 41 | .apache .hljs-cbracket, 42 | .hljs-date, 43 | .tex .hljs-command, 44 | .asciidoc .hljs-link_label, 45 | .markdown .hljs-link_label { 46 | color: #a5c261; 47 | } 48 | 49 | .hljs-subst { 50 | color: #519f50; 51 | } 52 | 53 | .hljs-tag, 54 | .hljs-tag .hljs-keyword, 55 | .hljs-tag .hljs-title, 56 | .hljs-doctype, 57 | .hljs-sub .hljs-identifier, 58 | .hljs-pi, 59 | .input_number { 60 | color: #e8bf6a; 61 | } 62 | 63 | .hljs-identifier { 64 | color: #d0d0ff; 65 | } 66 | 67 | .hljs-class .hljs-title, 68 | .hljs-type, 69 | .smalltalk .hljs-class, 70 | .hljs-javadoctag, 71 | .hljs-yardoctag, 72 | .hljs-phpdoc, 73 | .hljs-dartdoc { 74 | text-decoration: none; 75 | } 76 | 77 | .hljs-constant { 78 | color: #da4939; 79 | } 80 | 81 | 82 | .hljs-symbol, 83 | .hljs-built_in, 84 | .ruby .hljs-symbol .hljs-string, 85 | .ruby .hljs-symbol .hljs-identifier, 86 | .asciidoc .hljs-link_url, 87 | .markdown .hljs-link_url, 88 | .hljs-attribute { 89 | color: #6d9cbe; 90 | } 91 | 92 | .asciidoc .hljs-link_url, 93 | .markdown .hljs-link_url { 94 | text-decoration: underline; 95 | } 96 | 97 | 98 | 99 | .hljs-params, 100 | .hljs-variable, 101 | .clojure .hljs-attribute { 102 | color: #d0d0ff; 103 | } 104 | 105 | .css .hljs-tag, 106 | .hljs-rules .hljs-property, 107 | .hljs-pseudo, 108 | .tex .hljs-special { 109 | color: #cda869; 110 | } 111 | 112 | .css .hljs-class { 113 | color: #9b703f; 114 | } 115 | 116 | .hljs-rules .hljs-keyword { 117 | color: #c5af75; 118 | } 119 | 120 | .hljs-rules .hljs-value { 121 | color: #cf6a4c; 122 | } 123 | 124 | .css .hljs-id { 125 | color: #8b98ab; 126 | } 127 | 128 | .hljs-annotation, 129 | .apache .hljs-sqbracket, 130 | .nginx .hljs-built_in { 131 | color: #9b859d; 132 | } 133 | 134 | .hljs-preprocessor, 135 | .hljs-preprocessor *, 136 | .hljs-pragma { 137 | color: #8996a8 !important; 138 | } 139 | 140 | .hljs-hexcolor, 141 | .css .hljs-value .hljs-number { 142 | color: #a5c261; 143 | } 144 | 145 | .hljs-title, 146 | .hljs-decorator, 147 | .css .hljs-function { 148 | color: #ffc66d; 149 | } 150 | 151 | .diff .hljs-header, 152 | .hljs-chunk { 153 | background-color: #2f33ab; 154 | color: #e6e1dc; 155 | display: inline-block; 156 | width: 100%; 157 | } 158 | 159 | .diff .hljs-change { 160 | background-color: #4a410d; 161 | color: #f8f8f8; 162 | display: inline-block; 163 | width: 100%; 164 | } 165 | 166 | .hljs-addition { 167 | background-color: #144212; 168 | color: #e6e1dc; 169 | display: inline-block; 170 | width: 100%; 171 | } 172 | 173 | .hljs-deletion { 174 | background-color: #600; 175 | color: #e6e1dc; 176 | display: inline-block; 177 | width: 100%; 178 | } 179 | 180 | .coffeescript .javascript, 181 | .javascript .xml, 182 | .tex .hljs-formula, 183 | .xml .javascript, 184 | .xml .vbscript, 185 | .xml .css, 186 | .xml .hljs-cdata { 187 | opacity: 0.7; 188 | } 189 | -------------------------------------------------------------------------------- /content/themes/blablabla/App.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var PostList = require('./PostList'); 5 | var Single = require('./Single'); 6 | var ErrorPage = require('./Error'); 7 | var ApplicationStore = require('../../../shared/stores/ApplicationStore'); 8 | var MetaStore = require('../../../shared/stores/MetaStore'); 9 | var RouterMixin = require('flux-router-component').RouterMixin; 10 | var StoreMixin = require('fluxible').StoreMixin; 11 | var GoogleAnalytics = require('./GoogleAnalytics'); 12 | var assign = require('object-assign'); 13 | 14 | var App = React.createClass({ 15 | mixins: [RouterMixin, StoreMixin], 16 | statics: { 17 | storeListeners: [ApplicationStore, MetaStore] 18 | }, 19 | propTypes:{ 20 | context:React.PropTypes.object.isRequired 21 | }, 22 | getInitialState: function () { 23 | return assign(this.getStore(ApplicationStore).getState(), this.getStore(MetaStore).getState()); 24 | }, 25 | onChange: function () { 26 | this.setState(assign(this.getStore(ApplicationStore).getState(), this.getStore(MetaStore).getState())); 27 | }, 28 | sendGoogleAnalytics: function() { 29 | if (this.state.globals.googleAnalytics) { 30 | //ga 31 | window.ga('send', 'pageview', window.location.pathname); 32 | } 33 | }, 34 | highlightBlock: function() { 35 | //hljs 36 | var aCodes = document.getElementsByTagName('code'); 37 | for (var i=0; i < aCodes.length; i+=1) { 38 | window.hljs.highlightBlock(aCodes[i]); 39 | } 40 | }, 41 | componentDidMount: function() { 42 | this.highlightBlock(); 43 | this.sendGoogleAnalytics(); 44 | }, 45 | componentDidUpdate: function(prevProps, prevState) {// jshint ignore:line 46 | var newState = this.state; 47 | if (document.title !== (newState.meta.title || newState.globals.siteTitle)) { 48 | document.title = this.state.meta.title || this.state.globals.siteTitle; 49 | window.scrollTo(0,0); 50 | 51 | //handle metas 52 | var metaTag = document.getElementsByTagName('meta'); 53 | [].forEach.call(metaTag, function(meta){ 54 | if (meta.getAttribute('name') === 'description') { 55 | meta.content = this.state.meta.description; 56 | } 57 | }.bind(this)); 58 | 59 | this.highlightBlock(); 60 | this.sendGoogleAnalytics(); 61 | } 62 | }, 63 | render: function(){ 64 | var ga = this.state.globals.googleAnalytics ? : null; 65 | 66 | if (this.state.error) { 67 | return ( 68 |
69 | 70 | {ga} 71 |
72 | ); 73 | } 74 | if (this.state.route.name === 'home' || this.state.route.name === 'page') { 75 | return ( 76 |
77 | 78 | {ga} 79 |
80 | ); 81 | } 82 | if (this.state.route.name === 'post' || this.state.route.name === 'static') { 83 | return ( 84 |
85 | 86 | {ga} 87 |
88 | ); 89 | } 90 | /* 91 | if (this.state.route.name === 'tag') { 92 | return ( 93 |
94 | 95 | {clicky} 96 |
97 | ); 98 | } 99 | */ 100 | return null; 101 | } 102 | }); 103 | 104 | module.exports = App; 105 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var $ = require('gulp-load-plugins')(); 5 | var del = require('del'); 6 | var runSequence = require('run-sequence'); 7 | var argv = require('minimist')(process.argv.slice(2)); 8 | var moment = require('moment'); 9 | var webpack = require('webpack'); 10 | 11 | process.env.NODE_ENV = argv.env || 'development'; 12 | var configuration = require('./shared/config'); 13 | 14 | var config = { 15 | draft: './content/drafts/welcome-to-morpheus.md', 16 | posts: './content/posts/', 17 | client: './client/client.js', 18 | mainScss: './content/themes/' + configuration.theme + '/assets/scss/main.scss', 19 | scss: './content/themes/' + configuration.theme + '/assets/scss/**/*.scss', 20 | bundle: 'bundle.js', 21 | dist: './content/themes/' + configuration.theme + '/assets/dist', 22 | clientFiles: ['./client/**', './shared/**', './content/**'] 23 | }; 24 | 25 | gulp.task('clean', function(cb) { 26 | del([config.dist], cb); 27 | }); 28 | 29 | gulp.task('replace', function() { 30 | var manifestCss = require(config.dist + '/manifest-css.json'); 31 | var manifestJs = require(config.dist + '/manifest-js.json'); 32 | return gulp.src('./content/themes/' + configuration.theme + '/Html.js') 33 | .pipe($.replace(/bundle.*\.js/, manifestJs['bundle.js'])) 34 | .pipe($.replace(/main.*\.css/, manifestCss['main.css'])) 35 | .pipe(gulp.dest('./content/themes/' + configuration.theme)); 36 | }); 37 | 38 | gulp.task('replaceDev', function() { 39 | return gulp.src('./content/themes/' + configuration.theme + '/Html.js') 40 | .pipe($.replace(/bundle.*\.js/, 'bundle.js')) 41 | .pipe($.replace(/main.*\.css/, 'main.css')) 42 | .pipe(gulp.dest('./content/themes/' + configuration.theme)); 43 | }); 44 | 45 | gulp.task('styles', function() { 46 | return gulp.src(config.mainScss) 47 | .pipe($.changed(config.dist)) 48 | .pipe($.rubySass({ 49 | sourceMap: false 50 | })) 51 | .on('error', function(err) { 52 | $.util.log($.util.colors.red(err.message)); 53 | }) 54 | .pipe($.if(argv.env === 'production', $.cssmin())) 55 | .pipe($.if(argv.env === 'production', $.rev())) 56 | .pipe($.if(argv.env === 'production', $.rename(function(path) { 57 | path.basename += '.min'; 58 | }))) 59 | .pipe(gulp.dest(config.dist)) 60 | .pipe($.if(argv.env === 'production', $.rev.manifest({ 61 | path: 'manifest-css.json' 62 | }))) 63 | .pipe($.if(argv.env === 'production', gulp.dest(config.dist))); 64 | }); 65 | 66 | gulp.task('watchers', function() { 67 | gulp.watch(config.scss, ['styles']); 68 | gulp.watch(config.clientFiles, ['webpack']); 69 | }); 70 | 71 | var task = webpack(require('./webpack.config.' + process.env.NODE_ENV)); 72 | gulp.task('webpack', function(cb) { 73 | task.run(function(err, stats) { 74 | if (err) { 75 | throw new $.util.PluginError('webpack', err); 76 | } 77 | $.util.log('[webpack]', stats.toString({ 78 | colors: true, 79 | hash: false, 80 | timings: false, 81 | assets: true, 82 | chunks: false, 83 | chunkModules: false, 84 | modules: false, 85 | children: true 86 | })); 87 | cb(); 88 | }); 89 | }); 90 | 91 | gulp.task('server', function() { 92 | $.nodemon({ 93 | script: 'server.js', 94 | ext: 'js', 95 | ignore: ['client/*', 'content/*'], 96 | }) 97 | .on('restart', function() { 98 | $.util.log($.util.colors.red('restarted')); 99 | }); 100 | }); 101 | 102 | gulp.task('test', function() { 103 | return gulp.src(['test/*.js'], { 104 | read: false 105 | }) 106 | .pipe($.mocha({ 107 | reporter: 'spec' 108 | })) 109 | .on('error', $.util.log); 110 | }); 111 | 112 | gulp.task('install', function() { 113 | gulp.src(config.draft) 114 | .pipe($.rename({ 115 | prefix: moment().format('YYYY-MM-DD-HHmmss-') 116 | })) 117 | .pipe(gulp.dest(config.posts)); 118 | $.util.log($.util.colors.green('you are ready to go, run gulp watch')); 119 | }); 120 | 121 | gulp.task('build', ['clean'], function(cb) { 122 | runSequence('styles', 'webpack', 'replace', 'server', cb); 123 | }); 124 | 125 | gulp.task('watch', ['clean'], function(cb) { 126 | runSequence('styles', 'webpack', 'replaceDev', 'server', 'watchers', cb); 127 | }); 128 | 129 | gulp.task('default', function() { 130 | $.util.log($.util.colors.green('run gulp build || gulp watch')); 131 | }); 132 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/_base.scss: -------------------------------------------------------------------------------- 1 | @import 'normalize'; 2 | 3 | html { 4 | height: 100%; 5 | max-height: 100%; 6 | @include font-size($base-root-size); 7 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 8 | height: 100%; 9 | } 10 | body { 11 | height: 100%; 12 | max-height: 100%; 13 | font-family: "Merriweather", serif; 14 | letter-spacing: 0.01rem; 15 | @include font-size(18); 16 | line-height: 1.75em; 17 | color: #3a4145; 18 | -webkit-font-feature-settings: 'kern' 1; 19 | -moz-font-feature-settings: 'kern' 1; 20 | -o-font-feature-settings: 'kern' 1; 21 | height: 100%; 22 | } 23 | ::-moz-selection { 24 | 25 | background: #d6edff; 26 | } 27 | ::selection { 28 | 29 | background: #d6edff; 30 | } 31 | h1, 32 | h2, 33 | h3, 34 | h4, 35 | h5, 36 | h6 { 37 | -webkit-font-feature-settings: 'dlig' 1, 'liga' 1, 'lnum' 1, 'kern' 1; 38 | -moz-font-feature-settings: 'dlig' 1, 'liga' 1, 'lnum' 1, 'kern' 1; 39 | -o-font-feature-settings: 'dlig' 1, 'liga' 1, 'lnum' 1, 'kern' 1; 40 | color: $light-black; 41 | line-height: 1.15em; 42 | margin: 0 0 0.4em; 43 | font-family: "Open Sans", sans-serif; 44 | } 45 | h1 { 46 | @include font-size(50); 47 | letter-spacing: -2px; 48 | text-indent: -3px; 49 | } 50 | h2 { 51 | @include font-size(36); 52 | letter-spacing: -1px; 53 | } 54 | h3 { 55 | @include font-size(30); 56 | } 57 | h4 { 58 | @include font-size(25); 59 | } 60 | h5 { 61 | @include font-size(20); 62 | } 63 | h6 { 64 | @include font-size(20); 65 | } 66 | a { 67 | color: $blue; 68 | transition: color ease 0.3s; 69 | } 70 | a:hover { 71 | color: darken($blue,10); 72 | } 73 | p, 74 | ul, 75 | ol, 76 | dl { 77 | -webkit-font-feature-settings: 'liga' 1, 'onum' 1, 'kern' 1; 78 | -moz-font-feature-settings: 'liga' 1, 'onum' 1, 'kern' 1; 79 | -o-font-feature-settings: 'liga' 1, 'onum' 1, 'kern' 1; 80 | margin: 0 0 rem-calc(17.5); 81 | } 82 | ol, 83 | ul { 84 | padding-left: rem-calc(30) 85 | } 86 | ol ol, 87 | ul ul, 88 | ul ol, 89 | ol ul { 90 | margin: 0 0 rem-calc(4); 91 | padding-left: rem-calc(20); 92 | } 93 | dl dt { 94 | float: left; 95 | width: 180px; 96 | overflow: hidden; 97 | clear: left; 98 | text-align: right; 99 | text-overflow: ellipsis; 100 | white-space: nowrap; 101 | font-weight: 700; 102 | margin-bottom: 1em; 103 | } 104 | dl dd { 105 | margin-left: rem-calc(200); 106 | margin-bottom: rem-calc(10); 107 | } 108 | li { 109 | margin: rem-calc(4) 0; 110 | } 111 | li li { 112 | margin: 0; 113 | } 114 | hr { 115 | display: block; 116 | height: 1px; 117 | border: 0; 118 | border-top: #efefef 1px solid; 119 | margin: rem-calc(32) 0; 120 | padding: 0; 121 | } 122 | blockquote { 123 | box-sizing: border-box; 124 | margin: 1.75em 0px 1.75em -2.2em; 125 | padding: 0px 0px 0px 1.75em; 126 | border-left: 0.4em solid #4A4A4A; 127 | } 128 | blockquote p { 129 | margin: rem-calc(8) 0; 130 | font-style: italic; 131 | } 132 | blockquote small { 133 | display: inline-block; 134 | margin: 0.8em 0 0.8em 1.5em; 135 | @include font-size(9); 136 | color: #ccc; 137 | } 138 | blockquote small:before { 139 | content: "\2014 \00A0"; 140 | } 141 | blockquote cite { 142 | font-weight: 700; 143 | } 144 | blockquote cite a { 145 | font-weight: normal; 146 | } 147 | mark { 148 | background-color: #ffc336; 149 | } 150 | code, 151 | tt { 152 | padding: 1px 3px; 153 | font-family: Inconsolata, monospace, sans-serif; 154 | @include font-size(16); 155 | white-space: pre-wrap; 156 | border: #e3edf3 1px solid; 157 | background: #f7fafb; 158 | border-radius: 2px; 159 | } 160 | pre { 161 | -moz-box-sizing: border-box; 162 | box-sizing: border-box; 163 | margin: 0 0 1.75em; 164 | border: #e3edf3 1px solid; 165 | width: 100%; 166 | font-family: Inconsolata, monospace, sans-serif; 167 | @include font-size(16); 168 | white-space: pre; 169 | overflow: auto; 170 | background: #f7fafb; 171 | border-radius: 3px; 172 | } 173 | pre code, 174 | tt { 175 | font-size: inherit; 176 | white-space: pre-wrap; 177 | background: transparent; 178 | border: none; 179 | padding: 0; 180 | } 181 | kbd { 182 | display: inline-block; 183 | margin-bottom: 0.4em; 184 | padding: 1px 8px; 185 | border: #ccc 1px solid; 186 | color: #666; 187 | text-shadow: #fff 0 1px 0; 188 | @include font-size(16); 189 | font-weight: 700; 190 | background: #f4f4f4; 191 | border-radius: 4px; 192 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 1px 0 0 #fff inset; 193 | } 194 | table { 195 | -moz-box-sizing: border-box; 196 | box-sizing: border-box; 197 | margin: 1.75em 0; 198 | width: 100%; 199 | max-width: 100%; 200 | background-color: transparent; 201 | } 202 | table th, 203 | table td { 204 | padding: 8px; 205 | line-height: 20px; 206 | text-align: left; 207 | vertical-align: top; 208 | border-top: #efefef 1px solid; 209 | } 210 | table th { 211 | color: #000; 212 | } 213 | table caption + thead tr:first-child th, 214 | table caption + thead tr:first-child td, 215 | table colgroup + thead tr:first-child th, 216 | table colgroup + thead tr:first-child td, 217 | table thead:first-child tr:first-child th, 218 | table thead:first-child tr:first-child td { 219 | border-top: 0; 220 | } 221 | table tbody + tbody { 222 | border-top: #efefef 2px solid; 223 | } 224 | table table table { 225 | background-color: #fff; 226 | } 227 | table tbody > tr:nth-child(odd) > td, 228 | table tbody > tr:nth-child(odd) > th { 229 | background-color: #f6f6f6; 230 | } 231 | table.plain tbody > tr:nth-child(odd) > td, 232 | table.plain tbody > tr:nth-child(odd) > th { 233 | background: transparent; 234 | } 235 | iframe, 236 | .fluid-width-video-wrapper { 237 | display: block; 238 | margin: 1.75em 0; 239 | } 240 | 241 | .tag-text { 242 | font-weight: bold; 243 | } 244 | -------------------------------------------------------------------------------- /content/themes/blablabla/Single.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var cx = require('react/lib/cx'); 5 | var PostListElement = require('./PostListElement'); 6 | var ContentActions = require('../../../shared/actions/ContentActions'); 7 | var ContentStore = require('../../../shared/stores/ContentStore'); 8 | var InitialStateMixin = require('../../../shared/mixins/InitialStateMixin'); 9 | var Loader = require('./Loader'); 10 | var HeaderSingle = require('./HeaderSingle'); 11 | var Footer = require('./Footer'); 12 | var Tags = require('./Tags'); 13 | var StoreMixin = require('fluxible').StoreMixin; 14 | var gravatar = require('gravatar'); 15 | var NavLink = require('flux-router-component').NavLink; 16 | var ApplicationStore = require('../../../shared/stores/ApplicationStore'); 17 | var Disqus = require('./Disqus'); 18 | var Gravatar = require('./Gravatar'); 19 | 20 | var Single = React.createClass({ 21 | mixins: [StoreMixin], 22 | statics: { 23 | storeListeners: { 24 | _onChange: [ContentStore] 25 | } 26 | }, 27 | propTypes: { 28 | context: React.PropTypes.object.isRequired, 29 | params: React.PropTypes.object.isRequired 30 | }, 31 | getInitialState: function() { 32 | return this.getStateFromStores(); 33 | }, 34 | getStateFromStores: function () { 35 | return { 36 | single: this.getStore(ContentStore).getState().content, 37 | cssClass: 'single' 38 | }; 39 | }, 40 | _onChange: function() { 41 | this.setState(this.getStateFromStores()); 42 | }, 43 | componentDidMount: function() { 44 | if(!this.state.single.title){ 45 | this.props.context.executeAction(ContentActions.single, { 46 | slug: this.props.params.title 47 | }); 48 | } 49 | }, 50 | componentWillReceiveProps: function(props){ 51 | if (props.page !== this.props.page) { 52 | this.props.context.executeAction(ContentActions.single, { 53 | slug: props.params.title 54 | }); 55 | } 56 | }, 57 | componentWillUnmount: function(){ 58 | this.getStore(ContentStore).initialize(); 59 | }, 60 | render: function() { 61 | var showLoader = !this.state.single.title; 62 | var date = this.state.single.date; 63 | var style = { 64 | 'backgroundImage': 'url('+gravatar.url(this.state.single.email, {s:250}, true)+')' 65 | }; 66 | var classesMap = {}; 67 | classesMap.wrapper = true; 68 | classesMap[this.state.cssClass] = true; 69 | var classes = cx(classesMap); 70 | var footer = ''; 71 | var header = ''; 72 | var disqus = null; 73 | var siteGlobals = this.props.context.getStore(ApplicationStore).getState().globals; 74 | var authors = siteGlobals.authors; 75 | var authorMeta = authors[this.state.single.email] && authors[this.state.single.email].meta; 76 | var disqusComments = siteGlobals.disqusComments; 77 | if (this.state.single.type === 'post') { 78 | if (disqusComments) { 79 | disqus = ; 80 | } 81 | footer = 82 | 108 | 109 | header = 110 |
111 |

{this.state.single.title}

112 |
113 | 114 | {this.state.single.author} 115 | 116 | 117 |
118 |
119 | } 120 | return ( 121 |
122 | 123 |
124 |
125 | 126 |
127 | {header} 128 |
129 | {footer} 130 | {disqus} 131 |
132 |
133 |
134 |
135 |
136 | ); 137 | } 138 | }); 139 | 140 | module.exports = Single; 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Morpheus 2 | ### The next generation web publishing platform 3 |

4 | 5 |

6 | [![Build Status](https://secure.travis-ci.org/vesparny/morpheus.svg)](http://travis-ci.org/vesparny/morpheus) [![Dev dependencies status](https://david-dm.org/vesparny/morpheus/dev-status.svg?style=flat)](https://david-dm.org/vesparny/morpheus#info=devDependencies "Dependency status") [![dependencies status](https://david-dm.org/vesparny/morpheus/status.svg?style=flat)](https://david-dm.org/vesparny/morpheus#info=dependencies "Dependency status") 7 | 8 | **The idea is to create a new isomorphic web publishing platform, with the speed of a single page application, but server side rendered on the first load.** 9 | 10 | **Built with [React](http://facebook.github.io/react/), [express](http://expressjs.com/) and [browserify](http://browserify.org/).** 11 | 12 | ### Working demo 13 | 14 | You can see **Morpheus** running on my own [website](http://alessandro.arnodo.net). 15 | 16 | ### Articles 17 | 18 | * [Introducing Morpheus](http://alessandro.arnodo.net/2015/01/07/introducing-morpheus) 19 | 20 | ### What and Why 21 | 22 | At the time being, developers are building entire applications in the browser using JavaScript. The big part of the logic is living on the client and it talks to the server to an API. 23 | 24 | Once the application is fully loaded, the user can gain a good experience navigating between pages without the need of fully reloading each time. 25 | 26 | This is good, what happens when your website is run by a crawler (google bot or whatever)? If the website can only be executed on the client it won't be able to serve HTML to crawlers, and this will have negative impacts on SEO. 27 | 28 | This is why Morpheus is totally rendered on the server on the first load. Once done, React will attach events to the DOM, and the user will feel the benefits of a single page application, without having to wait for tedious spinners before seeing the content. 29 | 30 | 31 | ### Getting started 32 | 33 | Morpheus doesn't need a database, it just renders static markdown files. 34 | 35 | * Choose a name for you website, for this example we will call it `my-website`. Replace it with your name in the following commands. 36 | * Create a directory for your blog and create a new git repo 37 | 38 | ```shell 39 | mkdir my-website && cd my-website 40 | git init 41 | ``` 42 | 43 | * Checkout the `Morpheus` repository 44 | 45 | ```shell 46 | git remote add morpheus -m master https://github.com/vesparny/morpheus.git 47 | git pull -s recursive -X theirs morpheus master 48 | ``` 49 | 50 | * Install the dependencies, create the example post and run the application 51 | 52 | ```shell 53 | npm install 54 | gulp install #this is important, it will create an example post. 55 | gulp watch 56 | ``` 57 | 58 | * Access your fresh-new website at [http://localhost:3000](http://localhost:3000) 59 | 60 | * Create a new post or page inside the `content/posts` or `content/pages` folder, then commit changes. 61 | Please note that the filename structure must follow the convention `yyyy-mm-dd-HHmmss-post-title.md` 62 | Any page or post that contains a YAML front matter block will be processed by Morpheus as a special file. The front matter must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines. Take a look at the example post and page for more details. 63 | 64 | If you have an existing repository 65 | 66 | * Add the Morpheus remote to your local repository, then merge its master branch with your local branch. 67 | 68 | 69 | ```shell 70 | git remote add morpheus https://github.com/vesparny/morpheus.git 71 | git fetch morpheus 72 | git checkout master 73 | git merge morpheus/master 74 | ``` 75 | 76 | ### What’s done 77 | 78 | - [x] The basic technology stack (React express and browserify) 79 | - [x] Post and pages displaying, markdown render, posts pagination. 80 | - [x] Server side rendering. 81 | - [x] Comments managed with [Disqus](https://disqus.com/). 82 | - [x] Configurable permalinks. 83 | - [x] fully working default theme (it's called **blablabla**) 84 | - [x] RSS support 85 | 86 | 87 | ### What’s next 88 | 89 | Below is a list of the things to work on immediately, with links to the relevant discussion. 90 | 91 | - [ ] Logo design ([#3](https://github.com/vesparny/morpheus/issues/3)) 92 | - [ ] Sitemap generation ([#5](https://github.com/vesparny/morpheus/issues/5)) 93 | - [ ] Authors page ([#6](https://github.com/vesparny/morpheus/issues/6)) 94 | - [ ] Tag listing page ([#7](https://github.com/vesparny/morpheus/issues/7)) 95 | - [ ] Setup testing ([#8](https://github.com/vesparny/morpheus/issues/8)) 96 | - [ ] Reserve some routes for future development ([#9](https://github.com/vesparny/morpheus/issues/9)) 97 | - [ ] Split react components ([#10](https://github.com/vesparny/morpheus/issues/10)) 98 | - [ ] Create wiki taking inspiration from [Jekyll](http://jekyllrb.com/docs/home/) ([#11](https://github.com/vesparny/morpheus/issues/11)) 99 | - [ ] Create beautiful 404 and 500 pages and handle error also on the frontend ([#12](https://github.com/vesparny/morpheus/issues/12)) 100 | - [ ] Split Morpheus in smaller npm packages ([#13](https://github.com/vesparny/morpheus/issues/13)) 101 | - [ ] Publish to npm ([#14](https://github.com/vesparny/morpheus/issues/14)) 102 | 103 | 104 | Please feel free to join the discussions ;) 105 | 106 | 107 | 108 | ### Run in production 109 | 110 | * build the app for production, commit your production ready build, and run it. 111 | 112 | ```shell 113 | gulp build --env=production 114 | git add -A 115 | git commit -m "ready" 116 | NODE_ENV=production node server.js 117 | ``` 118 | 119 | ### Configuration 120 | 121 | You can also override configuration in the proper environment-specific configuration file inside the `config` folder. 122 | Below the production config file I use for hosting my website on OpenShift PaaS. 123 | 124 | ```javascript 125 | 'use strict'; 126 | 127 | var path = require('path'); 128 | 129 | module.exports = { 130 | log: { 131 | level: 'error', 132 | file: path.resolve(process.env.OPENSHIFT_DATA_DIR || '', 'log.log'), 133 | }, 134 | debug: false, 135 | siteUrl: 'https://alessandro.arnodo.net', 136 | useSSL: true, 137 | port: process.env.OPENSHIFT_NODEJS_PORT || 3000, 138 | ip: process.env.OPENSHIFT_NODEJS_IP || '127.0.0.1', 139 | disqusComments: 'arnodo', 140 | siteTitle: 'Alessandro Arnodo', 141 | siteDescription: '- Just another code monkey -', 142 | }; 143 | ``` 144 | 145 | ### Contributing 146 | 147 | PR and issues reporting are always welcome :) See more in CONTRIBUTING.md file. 148 | 149 | ### Contributors 150 | 151 | All this wouldn't have been possible without these great [contributors](https://github.com/vesparny/morpheus/graphs/contributors)! So THANK YOU! 152 | 153 | ### License 154 | 155 | Morpheus is open-source software released under the [MIT license](https://github.com/vesparny/morpheus/blob/master/LICENSE). 156 | 157 | ### Changelog 158 | 159 | See CHANGELOG.md file. 160 | 161 | ### Stability 162 | 163 | Currently Morpheus is in its very early stages, and it isn’t pretty. **It is far from usable.** Set it up only if you know what you’re doing, and expect it to break a lot. 164 | -------------------------------------------------------------------------------- /server/file-system-repository-strategy/content-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AbstractRepository = require('../abstract-repository'); 4 | var Promise = require('es6-promise').Promise; // jshint ignore:line 5 | var glob = require('glob'); 6 | var path = require('path'); 7 | var fs = require('fs'); 8 | var fm = require('front-matter'); 9 | var inherits = require('inherits'); 10 | var errors = require('../errors'); 11 | var moment = require('moment'); 12 | var assign = require('object-assign'); 13 | 14 | function buildContent(item, permalinkStructure) { 15 | var content = assign({}, item.attributes); 16 | content.permalink = '/' + content.slug + '/'; 17 | 18 | //sanitize 19 | content.body = item.body || ''; 20 | content.tags = content.tags ? content.tags.split(',') : []; 21 | if (content.type === 'post') { 22 | content.date = content.rawDate.format('DD MMMM YYYY'); 23 | var availablePatterns = { 24 | year: 'YYYY', 25 | month: 'MM', 26 | day: 'DD' 27 | }; 28 | var splitted = permalinkStructure.replace(/[\/]|[\/:]/g, ' ').split(' '); 29 | var pre = ''; 30 | splitted.forEach(function(el) { 31 | if (availablePatterns[el]) { 32 | pre += content.rawDate.format(availablePatterns[el]) + '/'; 33 | } 34 | }); 35 | content.permalink = '/' + pre + item.attributes.slug + '/'; 36 | } 37 | return content; 38 | } 39 | 40 | function readFile(file, permalinkStructure) { 41 | return new Promise(function(resolve, reject) { 42 | fs.readFile(file, { 43 | encoding: 'utf-8' 44 | }, function(err, data) { 45 | if (err) { 46 | reject(err); 47 | } else { 48 | var parsed = fm(data); 49 | var filename = path.basename(file); 50 | parsed.attributes.rawDate = moment(filename.substring(0, 17), 'YYYY-MM-DD-HHmmss'); 51 | var content = buildContent(parsed, permalinkStructure); 52 | resolve(content); 53 | } 54 | }); 55 | }); 56 | } 57 | 58 | 59 | function ContentRepository() { 60 | AbstractRepository.call(this); 61 | this.name = 'content-repository'; 62 | } 63 | 64 | inherits(ContentRepository, AbstractRepository); 65 | 66 | ContentRepository.prototype.find = function(options) { 67 | options = options || {}; 68 | var response = { 69 | meta: { 70 | page: options.page, 71 | perPage: options.postPerPage, 72 | pageCount: 0, 73 | totalCount: 0 74 | }, 75 | content: undefined 76 | }; 77 | return new Promise(function(resolve, reject) { 78 | glob(path.resolve(options.contentPath, 'posts') + '/**/*.md', function(err, files) { 79 | if (err) { 80 | reject(new errors.InternalServer()); 81 | } 82 | if (files.length === 0) { 83 | if (options.page === '1') { 84 | response.rawData = []; 85 | resolve(response); 86 | } else { 87 | reject(new errors.NotFound()); 88 | } 89 | } else { 90 | response.meta.totalCount = files.length; 91 | response.meta.pageCount = Math.ceil(files.length / response.meta.perPage); 92 | files.sort(function(a, b) { 93 | var filenameA = path.basename(a); 94 | var filenameB = path.basename(b); 95 | var dateA = moment(filenameA.substring(0, 17), 'YYYY-MM-DD-HHmmss'); 96 | var dateB = moment(filenameB.substring(0, 17), 'YYYY-MM-DD-HHmmss'); 97 | return dateB.unix() - dateA.unix(); 98 | }); 99 | var sliced = files.slice((response.meta.page - 1) * response.meta.perPage, response.meta.page * response.meta.perPage); 100 | if (sliced.length === 0) { 101 | reject(new errors.NotFound()); 102 | } 103 | var promiseArray = []; 104 | sliced.forEach(function(file) { 105 | promiseArray.push(readFile(file, options.permalinkStructure)); 106 | }); 107 | 108 | Promise.all(promiseArray).then(function(data) { 109 | response.content = data; 110 | resolve(response); 111 | }).catch(function(err) { 112 | reject(new errors.InternalServer(err.message)); 113 | }); 114 | } 115 | 116 | }); 117 | }); 118 | }; 119 | 120 | ContentRepository.prototype.getPostsForFeed = function(options) { 121 | options = options || {}; 122 | return new Promise(function(resolve, reject) { 123 | glob(path.resolve(options.contentPath, 'posts') + '/**/*.md', function(err, files) { 124 | if (err) { 125 | reject(new errors.InternalServer()); 126 | } 127 | files.sort(function(a, b) { 128 | var filenameA = path.basename(a); 129 | var filenameB = path.basename(b); 130 | var dateA = moment(filenameA.substring(0, 17), 'YYYY-MM-DD-HHmmss'); 131 | var dateB = moment(filenameB.substring(0, 17), 'YYYY-MM-DD-HHmmss'); 132 | return dateB.unix() - dateA.unix(); 133 | }); 134 | var sliced = files.slice(0, 10); 135 | var promiseArray = []; 136 | sliced.forEach(function(file) { 137 | promiseArray.push(readFile(file, options.permalinkStructure)); 138 | }); 139 | Promise.all(promiseArray).then(function(data) { 140 | resolve(data); 141 | }).catch(function(err) { 142 | reject(new errors.InternalServer(err.message)); 143 | }); 144 | }); 145 | }); 146 | }; 147 | 148 | ContentRepository.prototype.findOne = function(options) { 149 | options = options || {}; 150 | var that = this; 151 | var response = { 152 | meta: {}, 153 | content: undefined 154 | }; 155 | return new Promise(function(resolve, reject) { 156 | glob(path.resolve(options.contentPath, 'posts') + '/*[0-9][0-9][0-9][0-9][0-9][0-9]-' + options.slug + '.md', function(err, files) { 157 | if (err) { 158 | reject(new errors.InternalServer()); 159 | } 160 | if (files.length === 0) { 161 | that.findPage(options).then(function(data) { 162 | resolve(data); 163 | }).catch(function(err) { 164 | reject(err); 165 | }); 166 | } else { 167 | readFile(files[0], options.permalinkStructure).then(function(data) { 168 | response.content = data; 169 | resolve(response); 170 | }).catch(function(err) { 171 | reject(new errors.InternalServer(err.message)); 172 | }); 173 | } 174 | }); 175 | }); 176 | }; 177 | 178 | ContentRepository.prototype.findPage = function(options) { 179 | options = options || {}; 180 | var response = { 181 | meta: {}, 182 | content: undefined 183 | }; 184 | return new Promise(function(resolve, reject) { 185 | glob(path.resolve(options.contentPath, 'pages') + '/' + options.slug + '.md', function(err, files) { 186 | if (err) { 187 | reject(new errors.InternalServer(err.message)); 188 | } 189 | if (files.length === 0) { 190 | reject(new errors.NotFound()); 191 | } else { 192 | readFile(files[0]).then(function(data) { 193 | response.content = data; 194 | resolve(response); 195 | }).catch(function(err) { 196 | reject(new errors.InternalServer(err.message)); 197 | }); 198 | } 199 | }); 200 | }); 201 | }; 202 | 203 | ContentRepository.prototype.findTag = function(options) { 204 | var tag = options.tag; 205 | return Promise.resolve(tag); 206 | }; 207 | 208 | 209 | module.exports = ContentRepository; 210 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/_media_queries.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | 10. Media Queries - Smaller than 900px 3 | ========================================================================== */ 4 | @media only screen and (max-width: 900px) { 5 | .main-nav { 6 | padding: 15px; 7 | } 8 | blockquote { 9 | margin-left: 0; 10 | } 11 | .main-header { 12 | -webkit-box-sizing: border-box; 13 | -moz-box-sizing: border-box; 14 | box-sizing: border-box; 15 | height: auto; 16 | min-height: 240px; 17 | height: 60%; 18 | padding: 15% 0; 19 | } 20 | .scroll-down, 21 | .home .main-header:after { 22 | display: none; 23 | } 24 | .archive-template .main-header { 25 | min-height: 180px; 26 | padding: 10% 0; 27 | } 28 | .blog-logo img { 29 | padding: 4px 0; 30 | } 31 | .page-title { 32 | @include font-size(40); 33 | letter-spacing: -1px; 34 | } 35 | .page-description { 36 | @include font-size(18); 37 | line-height: 1.5em; 38 | } 39 | .post { 40 | @include font-size(16); 41 | } 42 | :not(.single) .post-title { 43 | @include font-size(32); 44 | } 45 | hr { 46 | margin: 2.4em 0; 47 | } 48 | ol, 49 | ul { 50 | padding-left: 2em; 51 | } 52 | h1 { 53 | @include font-size(45); 54 | text-indent: -2px; 55 | } 56 | h2 { 57 | @include font-size(36); 58 | } 59 | h3 { 60 | @include font-size(31); 61 | } 62 | h4 { 63 | @include font-size(25); 64 | } 65 | h5 { 66 | @include font-size(22); 67 | } 68 | h6 { 69 | @include font-size(18); 70 | } 71 | .author-profile { 72 | padding-bottom: rem-calc(40); 73 | } 74 | .author-profile .author-bio { 75 | @include font-size(16); 76 | } 77 | .author-meta span { 78 | display: block; 79 | margin: rem-calc(15) 0; 80 | } 81 | .author-profile .author-meta span { 82 | @include font-size(16); 83 | } 84 | .post-head.main-header { 85 | height: 45%; 86 | } 87 | .tag-head.main-header, 88 | .author-head.main-header { 89 | height: 30%; 90 | } 91 | .no-cover.post-head.main-header { 92 | height: 55px; 93 | padding: 0; 94 | } 95 | .no-cover.author-head.main-header { 96 | padding: 0; 97 | } 98 | 99 | } 100 | /* ========================================================================== 101 | 11. Media Queries - Smaller than 500px 102 | ========================================================================== */ 103 | @media only screen and (max-width: 500px) { 104 | .main-header { 105 | margin-bottom: 15px; 106 | height: 40%; 107 | } 108 | .no-cover.main-header { 109 | height: 30%; 110 | } 111 | .archive-template .main-header { 112 | max-height: 20%; 113 | min-height: 160px; 114 | padding: 10% 0; 115 | } 116 | .main-nav { 117 | padding: 0; 118 | margin-bottom: rem-calc(20); 119 | border-bottom: #e0e4e7 1px solid; 120 | } 121 | .blog-logo { 122 | display: none; 123 | } 124 | .blog-logo img { 125 | height: 26px; 126 | } 127 | .menu-button { 128 | width: 100%; 129 | border-radius: 0; 130 | font-size: rem-calc(12); 131 | float: none; 132 | } 133 | .menu-button.fright { 134 | width: 100%; 135 | float: none; 136 | } 137 | .menu-button:hover { 138 | border-color: #ebeef0; 139 | color: #2e2e2e; 140 | background: #ebeef0; 141 | } 142 | .menu-button.back-button { 143 | padding: 0 15px 0 10px; 144 | } 145 | .menu-button.subscribe-button.inverted, 146 | .menu-button.menu-button.inverted { 147 | padding: 0 12px; 148 | } 149 | .main-nav.overlay a:hover { 150 | color: #fff; 151 | border-color: transparent; 152 | background: transparent; 153 | } 154 | .no-cover .main-nav.overlay { 155 | background: none; 156 | } 157 | .no-cover .main-nav.overlay .back-button, 158 | .no-cover .main-nav.overlay .subscribe-button, 159 | .no-cover .main-nav.overlay .menu-button { 160 | border: none; 161 | } 162 | .main-nav.overlay .back-button, 163 | .main-nav.overlay .subscribe-button, 164 | .main-nav.overlay .menu-button { 165 | border-color: transparent; 166 | } 167 | .blog-logo img { 168 | max-height: 80px; 169 | } 170 | .inner, 171 | .pagination { 172 | width: auto; 173 | margin: rem-calc(20) auto; 174 | } 175 | .post { 176 | width: auto; 177 | margin: rem-calc(20); 178 | padding-bottom: rem-calc(20); 179 | line-height: rem-calc(29); 180 | } 181 | .post-date { 182 | display: none; 183 | } 184 | .single .post-header { 185 | margin-bottom: rem-calc(20); 186 | } 187 | .single .post-date { 188 | display: inline-block; 189 | } 190 | hr { 191 | margin: rem-calc(17) 0; 192 | } 193 | p, 194 | ul, 195 | ol, 196 | dl { 197 | @include font-size(16); 198 | margin: 0 0 rem-calc(25); 199 | } 200 | .page-title { 201 | @include font-size(30); 202 | } 203 | .post-excerpt p { 204 | @include font-size(15); 205 | } 206 | .page-description { 207 | @include font-size(16); 208 | } 209 | h1, 210 | h2, 211 | h3, 212 | h4, 213 | h5, 214 | h6 { 215 | margin: 0 0 0.3em; 216 | } 217 | h1 { 218 | @include font-size(28); 219 | letter-spacing: -1px; 220 | } 221 | h2 { 222 | @include font-size(24); 223 | letter-spacing: 0; 224 | } 225 | h3 { 226 | @include font-size(21); 227 | } 228 | h4 { 229 | @include font-size(19); 230 | } 231 | h5 { 232 | @include font-size(18); 233 | } 234 | h6 { 235 | @include font-size(18); 236 | } 237 | body:not(.single) .post-title { 238 | @include font-size(25); 239 | } 240 | .single .post { 241 | padding-bottom: 0; 242 | margin-bottom: 0; 243 | } 244 | .single .site-footer { 245 | margin-top: 0; 246 | } 247 | .post-content img { 248 | padding: 0; 249 | } 250 | .post-content .full-img { 251 | width: calc(100% + 32px); 252 | /* expand with to image + margins */ 253 | margin: 0 -16px; 254 | /* get rid of margins */ 255 | min-width: 0; 256 | max-width: 112%; 257 | /* fallback when calc doesn't work */ 258 | } 259 | .post-meta { 260 | @include font-size(13); 261 | margin-top: rem-calc(10); 262 | } 263 | .post-footer { 264 | padding: rem-calc(50) 0 rem-calc(30); 265 | text-align: center; 266 | } 267 | .post-footer .author { 268 | margin: 0 0 2rem; 269 | padding: 0 0 1.6rem; 270 | border-bottom: #ebf2f6 1px dashed; 271 | } 272 | .post-footer .share { 273 | position: static; 274 | width: auto; 275 | } 276 | .post-footer .share a { 277 | margin: rem-calc(14) rem-calc(8) 0; 278 | } 279 | .author-meta li { 280 | float: none; 281 | margin: 0; 282 | line-height: 1.75em; 283 | } 284 | .author-meta li:before { 285 | display: none; 286 | } 287 | .older-posts, 288 | .newer-posts { 289 | position: static; 290 | margin: 10px 0; 291 | } 292 | .page-number { 293 | display: block; 294 | } 295 | .site-footer { 296 | margin-top: rem-calc(30); 297 | } 298 | .author-profile { 299 | padding-bottom: rem-calc(20); 300 | } 301 | .post-head.main-header { 302 | height: 30%; 303 | } 304 | .tag-head.main-header, 305 | .author-head.main-header { 306 | height: 20%; 307 | } 308 | .author-profile .author-image { 309 | margin-top: -70px; 310 | } 311 | .author-profile .author-meta span { 312 | @include font-size(14); 313 | } 314 | .archive-template .main-header .page-description { 315 | display: none; 316 | } 317 | 318 | footer .theme { 319 | display: none; 320 | } 321 | 322 | footer .copyright, footer .poweredby { 323 | width: 100%; 324 | float: none; 325 | text-align: center; 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/layouts/_page.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | height: 100%; 3 | } 4 | .container { 5 | min-height: 100%; 6 | /* equal to footer height */ 7 | margin-bottom: -50px; 8 | } 9 | .container:after { 10 | content: ""; 11 | display: block; 12 | } 13 | .footer, 14 | .container:after { 15 | height: 50px; 16 | 17 | } 18 | 19 | .page-title { 20 | margin: 10px 0; 21 | @include font-size(50); 22 | letter-spacing: -1px; 23 | font-weight: 700; 24 | font-family: "Open Sans", sans-serif; 25 | color: #fff; 26 | text-transform: uppercase; 27 | } 28 | .page-description { 29 | margin: 0; 30 | @include font-size(20); 31 | line-height: 1.5em; 32 | font-weight: 400; 33 | font-family: "Merriweather", serif; 34 | letter-spacing: 0.01rem; 35 | color: rgba(255,255,255,0.8); 36 | } 37 | .no-cover .page-title { 38 | color: rgba(0,0,0,0.8); 39 | } 40 | .no-cover .page-description { 41 | color: rgba(0,0,0,0.5); 42 | } 43 | .no-cover .main-nav.overlay .back-button, 44 | .no-cover .main-nav.overlay .subscribe-button, 45 | .no-cover .main-nav.overlay .menu-button { 46 | color: rgba(0,0,0,0.4); 47 | border-color: rgba(0,0,0,0.3); 48 | 49 | } 50 | /* Add subtle load-in animation for content on the home page */ 51 | .home .page-title { 52 | -webkit-animation: fadeInDown 0.9s; 53 | animation: fadeInDown 0.9s; 54 | -webkit-animation-delay: 0.1s; 55 | animation-delay: 0.1s; 56 | } 57 | .home .page-description { 58 | -webkit-animation: fadeInDown 0.9s; 59 | animation: fadeInDown 0.9s; 60 | -webkit-animation-delay: 0.1s; 61 | animation-delay: 0.1s; 62 | 63 | } 64 | /* Every post, on every page, gets this style on its
tag */ 65 | .post { 66 | position: relative; 67 | width: 80%; 68 | max-width: 710px; 69 | margin: 4rem auto; 70 | padding-bottom: 4rem; 71 | border-bottom: #ebf2f6 1px solid; 72 | word-break: break-word; 73 | hyphens: auto; 74 | 75 | } 76 | /* Add a little circle in the middle of the border-bottom on our .post 77 | just for the lolz and stylepoints. */ 78 | .post:after { 79 | display: block; 80 | content: ""; 81 | width: 7px; 82 | height: 7px; 83 | border: #e7eef2 1px solid; 84 | position: absolute; 85 | bottom: -5px; 86 | left: 50%; 87 | margin-left: -5px; 88 | background: #fff; 89 | -webkit-border-radius: 100%; 90 | -moz-border-radius: 100%; 91 | border-radius: 100%; 92 | box-shadow: #fff 0 0 0 5px; 93 | } 94 | .post-title { 95 | @include font-size(36); 96 | } 97 | .single { 98 | .post-title{ 99 | @include font-size(50); 100 | } 101 | } 102 | .post-title a { 103 | text-decoration: none; 104 | } 105 | .post-excerpt p { 106 | margin: 0; 107 | @include font-size(16); 108 | line-height: 1.7em; 109 | } 110 | .read-more { 111 | text-decoration: none; 112 | } 113 | .post-meta { 114 | display: block; 115 | margin: 1.75rem 0 0; 116 | font-family: "Open Sans", sans-serif; 117 | @include font-size(15); 118 | line-height: rem-calc(23); 119 | color: #9eabb3; 120 | } 121 | .author-thumb { 122 | width: 24px; 123 | height: 24px; 124 | float: left; 125 | margin-right: 9px; 126 | border-radius: 100%; 127 | } 128 | .post-meta a { 129 | text-decoration: none; 130 | } 131 | .user-meta { 132 | position: relative; 133 | padding: 0.3rem 40px 0 100px; 134 | min-height: 77px; 135 | } 136 | .post-date { 137 | display: inline-block; 138 | margin-left: 8px; 139 | padding-left: 12px; 140 | border-left: #d5dbde 1px solid; 141 | text-transform: uppercase; 142 | @include font-size(13); 143 | white-space: nowrap; 144 | } 145 | .user-image { 146 | position: absolute; 147 | top: 0; 148 | left: 0; 149 | } 150 | .user-name { 151 | display: block; 152 | font-weight: 700; 153 | } 154 | .user-bio { 155 | display: block; 156 | max-width: 440px; 157 | @include font-size(14); 158 | line-height: 1.5em; 159 | } 160 | .publish-meta { 161 | position: absolute; 162 | top: 0; 163 | right: 0; 164 | padding: 4.3rem 0 4rem; 165 | text-align: right; 166 | } 167 | .publish-heading { 168 | display: block; 169 | font-weight: 700; 170 | } 171 | .publish-date { 172 | display: block; 173 | @include font-size(14); 174 | line-height: 1.5em; 175 | 176 | } 177 | 178 | .single .post-header { 179 | margin-bottom: 3.4rem; 180 | } 181 | .single .post-title { 182 | margin-bottom: 0; 183 | } 184 | .single .post-meta { 185 | margin: 0; 186 | } 187 | 188 | /* Stop .full-img from creating horizontal scroll - slight hack due to 189 | imperfections with browser width % calculations and rounding */ 190 | .single .content { 191 | overflow: hidden; 192 | 193 | } 194 | /* Tweak the .post wrapper style */ 195 | .single .post { 196 | margin-top: 0; 197 | border-bottom: none; 198 | padding-bottom: 0; 199 | 200 | } 201 | /* Kill that stylish little circle that was on the border, too */ 202 | .single .post:after { 203 | display: none; 204 | 205 | } 206 | /* Keep images centred and within the bounds of the post-width */ 207 | .post-content img { 208 | display: block; 209 | max-width: 100%; 210 | height: auto; 211 | margin: 0 auto; 212 | padding: 0.6em 0; 213 | 214 | } 215 | /* Break out larger images to be wider than the main text column 216 | the class is applied with jQuery */ 217 | .post-content .full-img { 218 | width: 126%; 219 | max-width: none; 220 | margin: 0 -13%; 221 | 222 | } 223 | /* The author credit area after the post */ 224 | .post-footer { 225 | position: relative; 226 | margin: 6rem 0 0; 227 | padding: 6rem 0 0; 228 | border-top: #ebf2f6 1px solid; 229 | } 230 | .post-footer h4 { 231 | @include font-size(18); 232 | margin: 0; 233 | } 234 | .post-footer p { 235 | margin: 1rem 0; 236 | @include font-size(14); 237 | line-height: 1.75em; 238 | 239 | } 240 | /* list of author links - location / url */ 241 | .author-meta { 242 | padding: 0; 243 | margin: 0; 244 | list-style: none; 245 | @include font-size(14); 246 | line-height: 1; 247 | font-style: italic; 248 | color: #9eabb3; 249 | } 250 | .author-meta a { 251 | color: #9eabb3; 252 | } 253 | .author-meta a:hover { 254 | color: #111; 255 | 256 | } 257 | /* Create some space to the right for the share links */ 258 | .post-footer .author { 259 | margin-right: 180px; 260 | } 261 | .post-footer h4 a { 262 | text-decoration: none; 263 | } 264 | .post-footer h4 a:hover { 265 | text-decoration: underline; 266 | 267 | } 268 | /* Drop the share links in the space to the right. 269 | Doing it like this means it's easier for the author bio 270 | to be flexible at smaller screen sizes while the share 271 | links remain at a fixed width the whole time */ 272 | .post-footer .share { 273 | position: absolute; 274 | top: 6rem; 275 | right: 0; 276 | width: 140px; 277 | } 278 | .post-footer .share a { 279 | @include font-size(22); 280 | display: inline-block; 281 | margin: 1rem 1.6rem 1.6rem 0; 282 | color: $gray; 283 | text-decoration: none; 284 | } 285 | .post-footer .share a:hover { 286 | color: $blue; 287 | 288 | } 289 | /* The subscribe icon on the footer */ 290 | .subscribe { 291 | width: 28px; 292 | height: 28px; 293 | position: absolute; 294 | top: -14px; 295 | left: 50%; 296 | margin-left: -15px; 297 | border: #ebf2f6 1px solid; 298 | text-align: center; 299 | line-height: 2.4rem; 300 | border-radius: 50px; 301 | background: #fff; 302 | transition: box-shadow 0.5s; 303 | 304 | } 305 | /* The RSS icon, inserted via icon font */ 306 | .subscribe:before { 307 | color: #d2dee3; 308 | @include font-size(10); 309 | position: absolute; 310 | top: 2px; 311 | left: 9px; 312 | font-weight: 700; 313 | transition: color 0.5s ease; 314 | 315 | } 316 | /* Add a box shadow to on hover */ 317 | .subscribe:hover { 318 | box-shadow: rgba(0,0,0,0.05) 0 0 0 3px; 319 | transition: box-shadow 0.25s; 320 | } 321 | .subscribe:hover:before { 322 | color: #50585d; 323 | 324 | } 325 | /* CSS tooltip saying "Subscribe!" - initially hidden */ 326 | .tooltip { 327 | opacity: 0; 328 | display: block; 329 | width: 53px; 330 | padding: 4px 8px 5px; 331 | position: absolute; 332 | top: -23px; 333 | left: -21px; 334 | color: rgba(255,255,255,0.9); 335 | @include font-size(11); 336 | line-height: 1em; 337 | text-align: center; 338 | background: #50585d; 339 | border-radius: 20px; 340 | box-shadow: 0 1px 4px rgba(0,0,0,0.1); 341 | transition: opacity 0.3s ease, top 0.3s ease; 342 | 343 | } 344 | /* The little chiclet arrow under the tooltip, pointing down */ 345 | .tooltip:after { 346 | content: " "; 347 | border-width: 5px 5px 0 5px; 348 | border-style: solid; 349 | border-color: #50585d transparent; 350 | display: block; 351 | position: absolute; 352 | bottom: -4px; 353 | left: 50%; 354 | margin-left: -5px; 355 | z-index: 220; 356 | width: 0; 357 | 358 | } 359 | /* On hover, show the tooltip! */ 360 | .subscribe:hover .tooltip { 361 | opacity: 1; 362 | top: -33px; 363 | } 364 | 365 | .loader { 366 | background: url("../img/loader.svg"); 367 | height: 24px; 368 | width: 30px; 369 | background-size: 30px 24px; 370 | position: relative; 371 | top: 30%; 372 | margin: 0 auto; 373 | } 374 | 375 | .tags { 376 | display: inline-block; 377 | margin-left: 5px; 378 | } 379 | 380 | .no-posts { 381 | color: $gray; 382 | font-weight: bold; 383 | } 384 | -------------------------------------------------------------------------------- /content/themes/blablabla/assets/scss/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /branding/morpheus wide nobg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | morpheus wide nobg 4 | Created with Sketch (http://www.bohemiancoding.com/sketch) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /branding/morpheus center nobg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | morpheus center nobg 4 | Created with Sketch (http://www.bohemiancoding.com/sketch) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /branding/morpheus center light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | morpheus center light 4 | Created with Sketch (http://www.bohemiancoding.com/sketch) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /branding/morpheus wide dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | morpheus wide dark 4 | Created with Sketch (http://www.bohemiancoding.com/sketch) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------