├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
24 |
27 |
28 |
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 |
--------------------------------------------------------------------------------
/branding/morpheus 16x16.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
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 | 
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 |
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 |
136 | );
137 | }
138 | });
139 |
140 | module.exports = Single;
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Morpheus
2 | ### The next generation web publishing platform
3 |
4 |
5 |
6 | [](http://travis-ci.org/vesparny/morpheus) [](https://david-dm.org/vesparny/morpheus#info=devDependencies "Dependency status") [](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 |
--------------------------------------------------------------------------------
/branding/morpheus center nobg.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/branding/morpheus center light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/branding/morpheus wide dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------