├── .babelrc ├── .gitignore ├── CHECKS ├── README.md ├── actions └── actions.js ├── app-client.js ├── app-server.js ├── bucket.json ├── components ├── App.js ├── Pages │ ├── Blog.js │ ├── Default.js │ ├── NoMatch.js │ └── Work.js └── Partials │ ├── BlogList.js │ ├── BlogSingle.js │ ├── Footer.js │ ├── Header.js │ ├── Loading.js │ ├── Nav.js │ ├── WorkList.js │ └── WorkSingle.js ├── config.js ├── dispatcher └── AppDispatcher.js ├── package-lock.json ├── package.json ├── public ├── css │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── clean-blog.css │ ├── clean-blog.min.css │ └── cosmic-custom.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── img │ ├── about-bg.jpg │ ├── contact-bg.jpg │ ├── home-bg.jpg │ ├── post-bg.jpg │ └── post-sample-image.jpg └── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── clean-blog.js │ ├── clean-blog.min.js │ ├── jquery.js │ └── jquery.min.js ├── routes.js ├── stores └── AppStore.js ├── views └── index.html ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets":[ 3 | "es2017", "react" 4 | ] 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | public/dist 5 | public/index.html -------------------------------------------------------------------------------- /CHECKS: -------------------------------------------------------------------------------- 1 | WAIT=30 2 | ATTEMPTS=10 3 | / DOCTYPE 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Universal Blog 2 | ##### [View a demo here](https://cosmicjs.com/apps/react-universal) 3 | [Sign up for Cosmic JS](https://cosmicjs.com/) to start managing content for your websites and applications faster and easier. 4 | 5 | ##### About 6 | The React Universal Blog is a portfolio blog app that renders html on the server to make all pages visible to search engines. Then after initial load from the server, it is converted to a single page application to allow for fast navigation between pages. 7 | 8 | ##### Notes 9 | I tried to keep the organization as simple as possible. There are no client, server or shared component folders. All components are shared and the only difference between client and server are the entry points (app-client.js, app-server.js and app.js). 10 | 11 | It uses the following: 12 |
13 | 1. [React](http://facebook.github.io/react/) for UI views
14 | 2. [Express](http://expressjs.com/) for server side rendering
15 | 3. [React Router](https://github.com/rackt/react-router) for routing
16 | 4. [React Hot Loader](https://github.com/gaearon/react-hot-loader) for hot loading in development
17 | 5. [Flux](https://facebook.github.io/flux/) for data flow
18 | 6. [Cosmic JS](https://cosmicjs.com) for content management 19 | ##### Install 20 | ``` 21 | git clone https://github.com/tonyspiro/react-universal-blog 22 | cd react-universal-blog 23 | npm install 24 | ``` 25 | ##### Run development 26 | ``` 27 | npm run development 28 | ``` 29 | Go to [http://localhost:8080](http://localhost:8080) 30 | ##### Run production 31 | ``` 32 | npm start 33 | ``` 34 | Go to [http://localhost:3000](http://localhost:3000) 35 | ##### Configure your Cosmic JS bucket 36 | After setting up your bucket on [Cosmic JS](https://cosmicjs.com), edit the ```config.js``` file and edit the slug to point to the slug of your bucket: 37 | ```javascript 38 | // config.js 39 | export default { 40 | bucket: { 41 | slug: 'react-universal-blog' 42 | } 43 | } 44 | ``` 45 | Cosmic JS makes a great [React CMS](https://cosmicjs.com/knowledge-base/react-cms) for your React apps. 46 | -------------------------------------------------------------------------------- /actions/actions.js: -------------------------------------------------------------------------------- 1 | // actions.js 2 | import config from '../config' 3 | import Cosmic from 'cosmicjs' 4 | import _ from 'lodash' 5 | 6 | // AppStore 7 | import AppStore from '../stores/AppStore' 8 | 9 | export function getStore(callback){ 10 | 11 | let pages = {} 12 | 13 | Cosmic.getObjects(config, function(err, response){ 14 | 15 | let objects = response.objects 16 | 17 | /* Globals 18 | ======================== */ 19 | let globals = AppStore.data.globals 20 | globals.text = response.object['text'] 21 | let metafields = globals.text.metafields 22 | let menu_title = _.find(metafields, { key: 'menu-title' }) 23 | globals.text.menu_title = menu_title.value 24 | 25 | let footer_text = _.find(metafields, { key: 'footer-text' }) 26 | globals.text.footer_text = footer_text.value 27 | 28 | let site_title = _.find(metafields, { key: 'site-title' }) 29 | globals.text.site_title = site_title.value 30 | 31 | // Social 32 | globals.social = response.object['social'] 33 | metafields = globals.social.metafields 34 | let twitter = _.find(metafields, { key: 'twitter' }) 35 | globals.social.twitter = twitter.value 36 | let facebook = _.find(metafields, { key: 'facebook' }) 37 | globals.social.facebook = facebook.value 38 | let github = _.find(metafields, { key: 'github' }) 39 | globals.social.github = github.value 40 | 41 | // Nav 42 | const nav_items = response.object['nav'].metafields 43 | globals.nav_items = nav_items 44 | 45 | AppStore.data.globals = globals 46 | 47 | /* Pages 48 | ======================== */ 49 | let pages = objects.type.page 50 | AppStore.data.pages = pages 51 | 52 | /* Articles 53 | ======================== */ 54 | let articles = objects.type['post'] 55 | articles = _.sortBy(articles, 'order') 56 | AppStore.data.articles = articles 57 | 58 | /* Work Items 59 | ======================== */ 60 | let work_items = objects.type['work'] 61 | work_items = _.sortBy(work_items, 'order') 62 | AppStore.data.work_items = work_items 63 | 64 | // Emit change 65 | AppStore.data.ready = true 66 | AppStore.emitChange() 67 | 68 | // Trigger callback (from server) 69 | if(callback){ 70 | callback(false, AppStore) 71 | } 72 | 73 | }) 74 | } 75 | 76 | export function getPageData(page_slug, post_slug){ 77 | 78 | if(!page_slug || page_slug === 'blog') 79 | page_slug = 'home' 80 | 81 | // Get page info 82 | const data = AppStore.data 83 | const pages = data.pages 84 | const page = _.find(pages, { slug: page_slug }) 85 | const metafields = page.metafields 86 | if(metafields){ 87 | const hero = _.find(metafields, { key: 'hero' }) 88 | page.hero = config.bucket.media_url + '/' + hero.value 89 | 90 | const headline = _.find(metafields, { key: 'headline' }) 91 | page.headline = headline.value 92 | 93 | const subheadline = _.find(metafields, { key: 'subheadline' }) 94 | page.subheadline = subheadline.value 95 | } 96 | 97 | if(post_slug){ 98 | if(page_slug === 'home'){ 99 | const articles = data.articles 100 | const article = _.find(articles, { slug: post_slug }) 101 | page.title = article.title 102 | } 103 | if(page_slug === 'work'){ 104 | const work_items = data.work_items 105 | const work_item = _.find(work_items, { slug: post_slug }) 106 | page.title = work_item.title 107 | } 108 | } 109 | AppStore.data.page = page 110 | AppStore.emitChange() 111 | } 112 | 113 | export function getMoreItems(){ 114 | 115 | AppStore.data.loading = true 116 | AppStore.emitChange() 117 | 118 | setTimeout(function(){ 119 | let item_num = AppStore.data.item_num 120 | let more_item_num = item_num + 5 121 | AppStore.data.item_num = more_item_num 122 | AppStore.data.loading = false 123 | AppStore.emitChange() 124 | }, 300) 125 | } -------------------------------------------------------------------------------- /app-client.js: -------------------------------------------------------------------------------- 1 | // app-client.js 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | import { Router } from 'react-router' 5 | import createBrowserHistory from 'history/lib/createBrowserHistory' 6 | const history = createBrowserHistory() 7 | 8 | // Routes 9 | import routes from './routes' 10 | 11 | const Routes = ( 12 | 13 | { routes } 14 | 15 | ) 16 | 17 | const app = document.getElementById('app') 18 | render(Routes, app) 19 | -------------------------------------------------------------------------------- /app-server.js: -------------------------------------------------------------------------------- 1 | // app-server.js 2 | import React from 'react' 3 | import { match, RoutingContext, Route, IndexRoute } from 'react-router' 4 | import ReactDOMServer from 'react-dom/server' 5 | import express from 'express' 6 | import hogan from 'hogan-express' 7 | import config from './config' 8 | 9 | // Actions 10 | import { getStore, getPageData } from './actions/actions' 11 | 12 | // Routes 13 | import routes from './routes' 14 | 15 | // Express 16 | const app = express() 17 | app.engine('html', hogan) 18 | app.set('views', __dirname + '/views') 19 | app.use('/', express.static(__dirname + '/public/')) 20 | app.set('port', (process.env.PORT || 3000)) 21 | 22 | app.get('*',(req, res) => { 23 | 24 | getStore(function(err, AppStore){ 25 | 26 | if(err){ 27 | return res.status(500).end('error') 28 | } 29 | 30 | match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { 31 | 32 | // Get page data for template 33 | const slug_arr = req.url.split('/') 34 | let page_slug = slug_arr[1] 35 | let post_slug 36 | if(page_slug === 'blog' || page_slug === 'work') 37 | post_slug = slug_arr[2] 38 | getPageData(page_slug, post_slug) 39 | const page = AppStore.data.page 40 | res.locals.page = page 41 | res.locals.site = config.site 42 | 43 | // Get React markup 44 | const reactMarkup = ReactDOMServer.renderToStaticMarkup() 45 | res.locals.reactMarkup = reactMarkup 46 | 47 | if (error) { 48 | 49 | res.status(500).send(error.message) 50 | 51 | } else if (redirectLocation) { 52 | 53 | res.redirect(302, redirectLocation.pathname + redirectLocation.search) 54 | 55 | } else if (renderProps) { 56 | 57 | // Success! 58 | res.status(200).render('index.html') 59 | 60 | } else { 61 | 62 | res.status(404).render('index.html') 63 | 64 | } 65 | }) 66 | 67 | }) 68 | }) 69 | 70 | app.listen(app.get('port')) 71 | 72 | console.info('==> ✅ Server is listening in ' + process.env.NODE_ENV + ' mode') 73 | console.info('==> 🌎 Go to http://localhost:%s', app.get('port')) 74 | -------------------------------------------------------------------------------- /bucket.json: -------------------------------------------------------------------------------- 1 | { 2 | "bucket": { 3 | "_id": "563c1dbe1ae7fe0f41000036", 4 | "slug": "react-universal-blog", 5 | "title": "React Universal Blog", 6 | "object_types": [ 7 | { 8 | "title": "Pages", 9 | "slug": "page", 10 | "singular": "Page", 11 | "order": 2 12 | }, 13 | { 14 | "title": "Posts", 15 | "slug": "post", 16 | "singular": "Post", 17 | "order": 3 18 | }, 19 | { 20 | "title": "Work", 21 | "slug": "work", 22 | "singular": "work", 23 | "order": 4 24 | }, 25 | { 26 | "title": "Globals", 27 | "slug": "globals", 28 | "singular": "globals", 29 | "help_text": null, 30 | "metafields": [], 31 | "options": { 32 | "slug_field": 0, 33 | "content_editor": 0, 34 | "add_metafields": 0, 35 | "metafields_title": 0, 36 | "metafields_key": 0 37 | }, 38 | "order": 1 39 | } 40 | ], 41 | "links": [ 42 | { 43 | "title": "GitHub Repo", 44 | "link": "https://github.com/tonyspiro/react-universal-blog" 45 | }, 46 | { 47 | "title": "Preview", 48 | "link": "http://react-universal-blog.cosmicapp.co/" 49 | } 50 | ], 51 | "objects": [ 52 | { 53 | "_id": "563c1def1ae7fe0f41000038", 54 | "bucket": "563c1dbe1ae7fe0f41000036", 55 | "slug": "text", 56 | "title": "Text", 57 | "content": "


", 58 | "metafields": [ 59 | { 60 | "edit": 1, 61 | "value": "React Universal Blog", 62 | "key": "site-title", 63 | "title": "Site Title", 64 | "type": "text", 65 | "children": false 66 | }, 67 | { 68 | "edit": 1, 69 | "value": "Cosmic JS", 70 | "key": "menu-title", 71 | "title": "Menu Title", 72 | "type": "text", 73 | "children": false 74 | }, 75 | { 76 | "edit": 1, 77 | "value": "Copyright © Cosmic JS 2015", 78 | "key": "footer-text", 79 | "title": "Footer Text", 80 | "type": "text", 81 | "children": false 82 | } 83 | ], 84 | "type_slug": "globals", 85 | "created": "2015-11-06T03:26:39.533Z", 86 | "user_id": "55767c3ffbcf5cbb13000001", 87 | "options": { 88 | "slug_field": 0, 89 | "content_editor": 0, 90 | "add_metafields": 0, 91 | "metafields_title": 0, 92 | "metafields_key": 0 93 | }, 94 | "order": null, 95 | "modified": "2015-11-06T03:41:04.284Z" 96 | }, 97 | { 98 | "_id": "563c1def1ae7fe0f4100003a", 99 | "bucket": "563c1dbe1ae7fe0f41000036", 100 | "slug": "social", 101 | "title": "Social", 102 | "content": "


", 103 | "metafields": [ 104 | { 105 | "edit": 1, 106 | "value": "https://twitter.com/cosmic_js", 107 | "key": "twitter", 108 | "title": "Twitter", 109 | "type": "text", 110 | "children": false 111 | }, 112 | { 113 | "edit": 1, 114 | "value": "https://facebook.com/cosmicjs", 115 | "key": "facebook", 116 | "title": "Facebook", 117 | "type": "text", 118 | "children": false 119 | }, 120 | { 121 | "edit": 1, 122 | "value": "https://github.com/tonyspiro/react-universal-blog", 123 | "key": "github", 124 | "title": "Github", 125 | "type": "text", 126 | "children": false 127 | } 128 | ], 129 | "type_slug": "globals", 130 | "created": "2015-11-06T03:26:39.535Z", 131 | "user_id": "55767c3ffbcf5cbb13000001", 132 | "options": { 133 | "slug_field": 0, 134 | "content_editor": 0, 135 | "add_metafields": 0, 136 | "metafields_title": 0, 137 | "metafields_key": 0 138 | }, 139 | "order": null, 140 | "modified": "2015-11-09T03:08:48.088Z" 141 | }, 142 | { 143 | "_id": "563c1fb51ae7fe0f41000086", 144 | "slug": "hello-universe", 145 | "title": "Hello Universe!", 146 | "content": "

This is the first post on your React Universal Blog.  You can add as many of these as you like!  Isn't that exciting?!  This cat thinks so.

", 147 | "metafields": [], 148 | "bucket": "563c1dbe1ae7fe0f41000036", 149 | "type_slug": "post", 150 | "created": "2015-11-06T03:34:13.437Z", 151 | "user_id": "55767c3ffbcf5cbb13000001", 152 | "options": { 153 | "slug_field": 0, 154 | "content_editor": 1, 155 | "add_metafields": 1, 156 | "metafields_title": 1, 157 | "metafields_key": 1 158 | }, 159 | "modified": "2016-07-27T19:30:56.547Z", 160 | "status": "published" 161 | }, 162 | { 163 | "_id": "563c20ad1ae7fe0f41000087", 164 | "slug": "work-item-title", 165 | "title": "Work Item Title", 166 | "content": "

This is an interesting work item isn't it?  Lumberg says, \"...yeah\"

", 167 | "metafields": [], 168 | "bucket": "563c1dbe1ae7fe0f41000036", 169 | "type_slug": "work", 170 | "created": "2015-11-06T03:38:21.580Z", 171 | "user_id": "55767c3ffbcf5cbb13000001", 172 | "options": { 173 | "slug_field": 0, 174 | "content_editor": 1, 175 | "add_metafields": 1, 176 | "metafields_title": 1, 177 | "metafields_key": 1 178 | }, 179 | "modified": "2016-07-27T19:32:03.060Z", 180 | "status": "published" 181 | }, 182 | { 183 | "_id": "567ad73780fb306b3a00000c", 184 | "slug": "nav", 185 | "title": "Nav", 186 | "content": "


", 187 | "metafields": [ 188 | { 189 | "edit": 1, 190 | "value": "", 191 | "key": "home", 192 | "title": "Home", 193 | "type": "text", 194 | "children": false 195 | }, 196 | { 197 | "edit": 1, 198 | "value": "work", 199 | "key": "work", 200 | "title": "Work", 201 | "type": "text", 202 | "children": false 203 | }, 204 | { 205 | "edit": 1, 206 | "value": "about", 207 | "key": "about", 208 | "title": "About", 209 | "type": "text", 210 | "children": false 211 | }, 212 | { 213 | "edit": 1, 214 | "value": "contact", 215 | "key": "contact", 216 | "title": "Contact", 217 | "type": "text", 218 | "children": false 219 | } 220 | ], 221 | "bucket": "563c1dbe1ae7fe0f41000036", 222 | "type_slug": "globals", 223 | "created": "2015-12-23T17:17:43.844Z", 224 | "user_id": "55767c3ffbcf5cbb13000001", 225 | "options": { 226 | "slug_field": 0, 227 | "content_editor": 0, 228 | "add_metafields": 0, 229 | "metafields_title": 0, 230 | "metafields_key": 0 231 | }, 232 | "modified": "2015-12-23T17:36:19.442Z" 233 | }, 234 | { 235 | "_id": "563c1def1ae7fe0f41000043", 236 | "bucket": "563c1dbe1ae7fe0f41000036", 237 | "slug": "home", 238 | "title": "Home", 239 | "content": "

This is some home page content...

", 240 | "metafields": [ 241 | { 242 | "edit": 1, 243 | "value": "45904530-8449-11e5-adfe-014902af5eff-space_11.jpg", 244 | "key": "hero", 245 | "title": "Hero", 246 | "type": "file", 247 | "children": false 248 | }, 249 | { 250 | "edit": 1, 251 | "value": "React Universal", 252 | "key": "headline", 253 | "title": "Headline", 254 | "type": "text", 255 | "children": false 256 | }, 257 | { 258 | "edit": 1, 259 | "value": "A Cosmic JS App Built with React", 260 | "key": "subheadline", 261 | "title": "Subheadline", 262 | "type": "text", 263 | "children": false 264 | } 265 | ], 266 | "type_slug": "page", 267 | "created": "2015-11-06T03:26:39.539Z", 268 | "user_id": "55767c3ffbcf5cbb13000001", 269 | "options": { 270 | "slug_field": 0, 271 | "content_editor": 1, 272 | "add_metafields": 1, 273 | "metafields_title": 1, 274 | "metafields_key": 1 275 | }, 276 | "order": 1, 277 | "modified": "2015-12-29T03:57:55.322Z" 278 | }, 279 | { 280 | "_id": "563c1def1ae7fe0f41000044", 281 | "bucket": "563c1dbe1ae7fe0f41000036", 282 | "slug": "work", 283 | "title": "Work", 284 | "content": "


", 285 | "metafields": [ 286 | { 287 | "edit": 1, 288 | "value": "699270f0-77bc-11e5-bea6-3f1da2fe3a09-6a6b6eb0-6f5f-11e5-9c06-3fa77abead52-outer-space-1920x1200-hd-wallpaper (1).jpg", 289 | "key": "hero", 290 | "title": "Hero", 291 | "type": "file", 292 | "children": false 293 | }, 294 | { 295 | "edit": 1, 296 | "value": "Work Stuff", 297 | "key": "headline", 298 | "title": "Headline", 299 | "type": "text", 300 | "children": false 301 | }, 302 | { 303 | "edit": 1, 304 | "value": "All work and no play make for a dull demo", 305 | "key": "subheadline", 306 | "title": "Subheadline", 307 | "type": "text", 308 | "children": false 309 | } 310 | ], 311 | "type_slug": "page", 312 | "created": "2015-11-06T03:26:39.540Z", 313 | "user_id": "55767c3ffbcf5cbb13000001", 314 | "options": null, 315 | "order": 2, 316 | "modified": "2015-11-06T06:14:09.592Z" 317 | }, 318 | { 319 | "_id": "563c1def1ae7fe0f41000047", 320 | "bucket": "563c1dbe1ae7fe0f41000036", 321 | "slug": "about", 322 | "title": "About", 323 | "content": "Hi, I'm a React Universal Blog powered by the Cosmic JS content platform.  You can enjoy me on the server or the browser.  If you are a Google robot, feel free to navigate through all of my pages and add them to your search index!  If you are a human, enjoy browsing my pages super fast because I'm also a single page application!

Click here to view my source.

Cheers!
React Universal Blog
", 324 | "metafields": [ 325 | { 326 | "edit": 1, 327 | "value": "99e55210-8449-11e5-adfe-014902af5eff-steamworkshop_webupload_previewfile_327002106_preview.jpg", 328 | "key": "hero", 329 | "title": "Hero", 330 | "type": "file", 331 | "children": false 332 | }, 333 | { 334 | "edit": 1, 335 | "value": "About Me", 336 | "key": "headline", 337 | "title": "Headline", 338 | "type": "text", 339 | "children": false 340 | }, 341 | { 342 | "edit": 1, 343 | "value": "I'll tell you some stuff then I'll ask about you", 344 | "key": "subheadline", 345 | "title": "Subheadline", 346 | "type": "text", 347 | "children": false 348 | } 349 | ], 350 | "type_slug": "page", 351 | "created": "2015-11-06T03:26:39.542Z", 352 | "user_id": "55767c3ffbcf5cbb13000001", 353 | "options": null, 354 | "order": 3, 355 | "modified": "2015-11-06T06:19:34.580Z" 356 | }, 357 | { 358 | "_id": "563c1def1ae7fe0f41000049", 359 | "bucket": "563c1dbe1ae7fe0f41000036", 360 | "slug": "contact", 361 | "title": "Contact", 362 | "content": "
Like what I do?  Hit me up at support@cosmicjs.com
", 363 | "metafields": [ 364 | { 365 | "edit": 1, 366 | "value": "0e2445e0-844b-11e5-adfe-014902af5eff-deep_space_nebula_by_hameed.jpg", 367 | "key": "hero", 368 | "title": "Hero", 369 | "type": "file", 370 | "children": false 371 | }, 372 | { 373 | "edit": 1, 374 | "value": "Contact Me", 375 | "key": "headline", 376 | "title": "Headline", 377 | "type": "text", 378 | "children": false 379 | }, 380 | { 381 | "edit": 1, 382 | "value": "If you need anything, sugar, advice, I try to be a good neighbor", 383 | "key": "subheadline", 384 | "title": "Subheadline", 385 | "type": "text", 386 | "children": false 387 | } 388 | ], 389 | "type_slug": "page", 390 | "created": "2015-11-06T03:26:39.543Z", 391 | "user_id": "55767c3ffbcf5cbb13000001", 392 | "options": null, 393 | "order": 4, 394 | "modified": "2015-11-06T06:08:00.568Z" 395 | } 396 | ], 397 | "media": [ 398 | { 399 | "_id": "563c3d8e1ae7fe0f4100008a", 400 | "name": "0a253dc0-8449-11e5-adfe-014902af5eff-steamworkshop_webupload_previewfile_327002106_preview.jpg", 401 | "original_name": "steamworkshop_webupload_previewfile_327002106_preview.jpg", 402 | "size": 145499, 403 | "type": "image/jpeg", 404 | "bucket": "563c1dbe1ae7fe0f41000036", 405 | "created": "2015-11-06T05:41:34.264Z", 406 | "folder": null, 407 | "location": "https://cosmicjs.com/uploads" 408 | }, 409 | { 410 | "_id": "563c3df11ae7fe0f4100008b", 411 | "name": "45904530-8449-11e5-adfe-014902af5eff-space_11.jpg", 412 | "original_name": "space_11.jpg", 413 | "size": 186217, 414 | "type": "image/jpeg", 415 | "bucket": "563c1dbe1ae7fe0f41000036", 416 | "created": "2015-11-06T05:43:13.933Z", 417 | "folder": null, 418 | "location": "https://cosmicjs.com/uploads" 419 | }, 420 | { 421 | "_id": "563c3e7f1ae7fe0f4100008d", 422 | "name": "99e55210-8449-11e5-adfe-014902af5eff-steamworkshop_webupload_previewfile_327002106_preview.jpg", 423 | "original_name": "steamworkshop_webupload_previewfile_327002106_preview.jpg", 424 | "size": 145499, 425 | "type": "image/jpeg", 426 | "bucket": "563c1dbe1ae7fe0f41000036", 427 | "created": "2015-11-06T05:45:35.418Z", 428 | "folder": null, 429 | "location": "https://cosmicjs.com/uploads" 430 | }, 431 | { 432 | "_id": "563c40ef1ae7fe0f4100008e", 433 | "name": "0e2445e0-844b-11e5-adfe-014902af5eff-deep_space_nebula_by_hameed.jpg", 434 | "original_name": "deep_space_nebula_by_hameed.jpg", 435 | "size": 276879, 436 | "type": "image/jpeg", 437 | "bucket": "563c1dbe1ae7fe0f41000036", 438 | "created": "2015-11-06T05:55:59.945Z", 439 | "folder": null, 440 | "location": "https://cosmicjs.com/uploads" 441 | }, 442 | { 443 | "_id": "57990bc3ed7a775367002aee", 444 | "name": "88c45370-5430-11e6-a6a3-41ff49de9ecd-download.jpeg", 445 | "original_name": "download.jpeg", 446 | "size": 124142, 447 | "type": "image/jpeg", 448 | "bucket": "563c1dbe1ae7fe0f41000036", 449 | "created": "2016-07-27T19:30:11.116Z", 450 | "folder": null, 451 | "location": "https://cosmicjs.com/uploads" 452 | }, 453 | { 454 | "_id": "57990c15ed7a775367002b02", 455 | "name": "b9d4e740-5430-11e6-a6a3-41ff49de9ecd-download (1).jpeg", 456 | "original_name": "download (1).jpeg", 457 | "size": 561637, 458 | "type": "image/jpeg", 459 | "bucket": "563c1dbe1ae7fe0f41000036", 460 | "created": "2016-07-27T19:31:33.444Z", 461 | "folder": null, 462 | "location": "https://cosmicjs.com/uploads" 463 | } 464 | ], 465 | "media_folders": null, 466 | "plugins": null 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /components/App.js: -------------------------------------------------------------------------------- 1 | // App.js 2 | import React, { Component } from 'react' 3 | 4 | // Dispatcher 5 | import AppDispatcher from '../dispatcher/AppDispatcher' 6 | 7 | // Store 8 | import AppStore from '../stores/AppStore' 9 | 10 | // Components 11 | import Nav from './Partials/Nav' 12 | import Footer from './Partials/Footer' 13 | import Loading from './Partials/Loading' 14 | 15 | export default class App extends Component { 16 | 17 | // Add change listeners to stores 18 | componentDidMount(){ 19 | AppStore.addChangeListener(this._onChange.bind(this)) 20 | } 21 | 22 | // Remove change listeners from stores 23 | componentWillUnmount(){ 24 | AppStore.removeChangeListener(this._onChange.bind(this)) 25 | } 26 | 27 | getStore(){ 28 | AppDispatcher.dispatch({ 29 | action: 'get-app-store' 30 | }) 31 | } 32 | 33 | _onChange(){ 34 | this.setState(AppStore) 35 | } 36 | 37 | render(){ 38 | 39 | const data = AppStore.data 40 | 41 | // Show loading for browser 42 | if(!data.ready){ 43 | 44 | document.body.className = '' 45 | this.getStore() 46 | 47 | let style = { 48 | marginTop: 120 49 | } 50 | return ( 51 |
52 | 53 |
54 | ) 55 | } 56 | 57 | // Server first 58 | const Routes = React.cloneElement(this.props.children, { data: data }) 59 | 60 | return ( 61 |
62 |
66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /components/Pages/Blog.js: -------------------------------------------------------------------------------- 1 | // Blog.js 2 | import React, { Component } from 'react' 3 | import _ from 'lodash' 4 | import config from '../../config' 5 | 6 | // Components 7 | import Header from '../Partials/Header' 8 | import BlogList from '../Partials/BlogList' 9 | import BlogSingle from '../Partials/BlogSingle' 10 | 11 | // Dispatcher 12 | import AppDispatcher from '../../dispatcher/AppDispatcher' 13 | 14 | export default class Blog extends Component { 15 | 16 | componentWillMount(){ 17 | this.getPageData() 18 | } 19 | 20 | componentDidMount(){ 21 | const data = this.props.data 22 | document.title = config.site.title + ' | ' + data.page.title 23 | } 24 | 25 | getPageData(){ 26 | AppDispatcher.dispatch({ 27 | action: 'get-page-data', 28 | page_slug: 'blog', 29 | post_slug: this.props.params.slug 30 | }) 31 | } 32 | 33 | getMoreArticles(){ 34 | AppDispatcher.dispatch({ 35 | action: 'get-more-items' 36 | }) 37 | } 38 | 39 | render(){ 40 | 41 | const data = this.props.data 42 | const globals = data.globals 43 | const pages = data.pages 44 | let main_content 45 | 46 | if(!this.props.params.slug){ 47 | 48 | main_content = 49 | 50 | } else { 51 | 52 | const articles = data.articles 53 | 54 | // Get current page slug 55 | const slug = this.props.params.slug 56 | const articles_object = _.keyBy(articles, 'slug') 57 | const article = articles_object[slug] 58 | main_content = 59 | 60 | } 61 | 62 | return ( 63 |
64 |
65 |
66 |
67 |
68 | { main_content } 69 |
70 |
71 |
72 |
73 | ) 74 | } 75 | } -------------------------------------------------------------------------------- /components/Pages/Default.js: -------------------------------------------------------------------------------- 1 | // Default.js 2 | import React, { Component } from 'react' 3 | import { Link } from 'react-router' 4 | import config from '../../config' 5 | 6 | // Components 7 | import Header from '../Partials/Header' 8 | 9 | // Dispatcher 10 | import AppDispatcher from '../../dispatcher/AppDispatcher' 11 | 12 | export default class Default extends Component { 13 | 14 | componentWillMount(){ 15 | this.getPageData() 16 | } 17 | 18 | componentDidUpdate(){ 19 | const data = this.props.data 20 | document.title = config.site.title + ' | ' + data.page.title 21 | 22 | // Updated 23 | const page = data.page 24 | const page_slug = this.getSlug() 25 | if(page.slug !== page_slug) 26 | this.getPageData(page_slug) 27 | } 28 | 29 | getSlug(){ 30 | return this.props.location.pathname.replace('/','') 31 | } 32 | 33 | getPageData(){ 34 | const page_slug = this.getSlug() 35 | AppDispatcher.dispatch({ 36 | action: 'get-page-data', 37 | page_slug: page_slug 38 | }) 39 | } 40 | 41 | render(){ 42 | 43 | const slug = this.getSlug() 44 | const data = this.props.data 45 | const page = data.page 46 | 47 | let main_content =
48 | 49 | return ( 50 |
51 |
52 |
53 |
54 |
55 | { main_content } 56 |
57 |
58 |
59 |
60 | ) 61 | } 62 | } -------------------------------------------------------------------------------- /components/Pages/NoMatch.js: -------------------------------------------------------------------------------- 1 | // NoMatch.js 2 | import React, { Component } from 'react' 3 | import { Link } from 'react-router' 4 | import config from '../../config' 5 | 6 | // Components 7 | import Header from '../Partials/Header' 8 | 9 | // Dispatcher 10 | import AppDispatcher from '../../dispatcher/AppDispatcher' 11 | 12 | export default class NoMatch extends Component { 13 | 14 | componentWillMount(){ 15 | this.getPageData() 16 | } 17 | 18 | componentDidMount(){ 19 | const data = this.props.data 20 | document.title = config.site.title + ' | Page Not Found' 21 | } 22 | 23 | getPageData(){ 24 | AppDispatcher.dispatch({ 25 | action: 'get-page-data', 26 | slug: 'home' 27 | }) 28 | } 29 | 30 | render(){ 31 | 32 | const data = this.props.data 33 | const page = data.page 34 | 35 | return ( 36 |
37 |
38 |
39 |
40 |
41 |
42 | Whoa! Looks like you stumbled down a worm hole!
43 | If this is a new page that you've added in Cosmic JS, make sure you add it to your routes.js file! 44 |
45 |
46 | Take me home 47 |
48 |
49 |
50 |
51 |
52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /components/Pages/Work.js: -------------------------------------------------------------------------------- 1 | // Work.js 2 | import React, { Component } from 'react' 3 | import _ from 'lodash' 4 | import config from '../../config' 5 | 6 | // Components 7 | import Header from '../Partials/Header' 8 | import WorkList from '../Partials/WorkList' 9 | import WorkSingle from '../Partials/WorkSingle' 10 | 11 | // Dispatcher 12 | import AppDispatcher from '../../dispatcher/AppDispatcher' 13 | 14 | export default class Work extends Component { 15 | 16 | componentWillMount(){ 17 | this.getPageData() 18 | } 19 | 20 | componentDidMount(){ 21 | const data = this.props.data 22 | document.title = config.site.title + ' | ' + data.page.title 23 | } 24 | 25 | getPageData(){ 26 | AppDispatcher.dispatch({ 27 | action: 'get-page-data', 28 | page_slug: 'work', 29 | post_slug: this.props.params.slug 30 | }) 31 | } 32 | 33 | getMoreWorkItems(){ 34 | AppDispatcher.dispatch({ 35 | action: 'get-more-items' 36 | }) 37 | } 38 | 39 | render(){ 40 | 41 | const data = this.props.data 42 | const globals = data.globals 43 | const pages = data.pages 44 | let main_content 45 | 46 | if(!this.props.params.slug){ 47 | 48 | main_content = 49 | 50 | } else { 51 | 52 | const work_items = data.work_items 53 | 54 | // Get current page slug 55 | const slug = this.props.params.slug 56 | const work_items_object = _.indexBy(work_items, 'slug') 57 | const work_item = work_items_object[slug] 58 | 59 | main_content = 60 | 61 | } 62 | 63 | return ( 64 |
65 |
66 |
67 |
68 |
69 | { main_content } 70 |
71 |
72 |
73 |
74 | ) 75 | } 76 | } -------------------------------------------------------------------------------- /components/Partials/BlogList.js: -------------------------------------------------------------------------------- 1 | // BlogList.js 2 | import React, { Component } from 'react' 3 | import _ from 'lodash' 4 | import { Link } from 'react-router' 5 | 6 | export default class BlogList extends Component { 7 | 8 | scrollTop(){ 9 | $('html, body').animate({ 10 | scrollTop: $("#main-content").offset().top 11 | }, 500) 12 | } 13 | 14 | render(){ 15 | 16 | let data = this.props.data 17 | let item_num = data.item_num 18 | let articles = data.articles 19 | 20 | let load_more 21 | let show_more_text = 'Show More Articles' 22 | 23 | if(data.loading){ 24 | show_more_text = 'Loading...' 25 | } 26 | 27 | if(articles && item_num <= articles.length){ 28 | load_more = ( 29 |
30 | 33 |
34 | ) 35 | } 36 | 37 | articles = _.take(articles, item_num) 38 | 39 | let articles_html = articles.map(( article ) => { 40 | let date_obj = new Date(article.created) 41 | let created = (date_obj.getMonth()+1) + '/' + date_obj.getDate() + '/' + date_obj.getFullYear() 42 | return ( 43 |
44 |
45 |

46 | { article.title } 47 |

48 |

Posted by Cosmic JS on { created }

49 |
50 |
51 |
52 | ) 53 | }) 54 | 55 | return ( 56 |
57 |
{ articles_html }
58 | { load_more } 59 |
60 | ) 61 | } 62 | } -------------------------------------------------------------------------------- /components/Partials/BlogSingle.js: -------------------------------------------------------------------------------- 1 | // BlogSingle.js 2 | import React, { Component } from 'react' 3 | import { Link } from 'react-router' 4 | 5 | export default class BlogSingle extends Component { 6 | 7 | render(){ 8 | 9 | const article = this.props.article 10 | 11 | const style = { 12 | marginBottom: 20 13 | } 14 | 15 | return ( 16 |
17 | << Back to Article List 18 |

{ article.title }

19 |
20 |
21 | ) 22 | } 23 | } -------------------------------------------------------------------------------- /components/Partials/Footer.js: -------------------------------------------------------------------------------- 1 | // Footer.js 2 | import React, { Component } from 'react' 3 | 4 | export default class Footer extends Component { 5 | 6 | render(){ 7 | 8 | const data = this.props.data 9 | let footer_text 10 | if(data.globals.text){ 11 | footer_text = data.globals.text.footer_text 12 | } 13 | 14 | let twitter 15 | let facebook 16 | let github 17 | if(data.globals.social){ 18 | twitter = data.globals.social.twitter 19 | facebook = data.globals.social.facebook 20 | github = data.globals.social.github 21 | } 22 | 23 | return ( 24 | 59 | ) 60 | } 61 | } -------------------------------------------------------------------------------- /components/Partials/Header.js: -------------------------------------------------------------------------------- 1 | // Header.js 2 | import React, { Component } from 'react' 3 | 4 | export default class Header extends Component { 5 | 6 | render(){ 7 | 8 | const data = this.props.data 9 | const hero = data.page.hero 10 | const headline = data.page.headline 11 | const subheadline = data.page.subheadline 12 | 13 | return ( 14 |
15 |
16 |
17 |
18 |
19 |
20 |

{ headline }

21 |
22 | { subheadline } 23 |
24 |
25 |
26 |
27 |
28 |
29 | ) 30 | } 31 | } -------------------------------------------------------------------------------- /components/Partials/Loading.js: -------------------------------------------------------------------------------- 1 | // Loading.js 2 | import React, { Component } from 'react' 3 | 4 | export default class Loading extends Component { 5 | render(){ 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ) 22 | } 23 | } -------------------------------------------------------------------------------- /components/Partials/Nav.js: -------------------------------------------------------------------------------- 1 | // Nav.js 2 | import React, { Component } from 'react' 3 | import { Link } from 'react-router' 4 | 5 | export default class Nav extends Component { 6 | 7 | handleClick(){ 8 | $('.navbar-collapse').removeClass('in') 9 | $('html,body').scrollTop(0) 10 | } 11 | 12 | render(){ 13 | 14 | const data = this.props.data 15 | const nav_items = data.globals.nav_items 16 | 17 | // Prevent initial null 18 | if(!nav_items){ 19 | return
20 | } 21 | 22 | const menu_items = nav_items.map(( nav_item ) => { 23 | return ( 24 |
  • 25 | { nav_item.title } 26 |
  • 27 | ) 28 | }) 29 | 30 | return ( 31 | 50 | ) 51 | } 52 | } -------------------------------------------------------------------------------- /components/Partials/WorkList.js: -------------------------------------------------------------------------------- 1 | // WorkList.js 2 | import React, { Component } from 'react' 3 | import _ from 'lodash' 4 | import { Link } from 'react-router' 5 | 6 | // Dispatcher 7 | import AppDispatcher from '../../dispatcher/AppDispatcher' 8 | 9 | export default class WorkList extends Component { 10 | 11 | scrollTop(){ 12 | $('html, body').animate({ 13 | scrollTop: $("#main-content").offset().top 14 | }, 500) 15 | } 16 | 17 | render(){ 18 | 19 | let data = this.props.data 20 | let item_num = data.item_num 21 | let work_items = data.work_items 22 | 23 | let load_more 24 | let show_more_text = 'Show More Work' 25 | 26 | if(data.loading){ 27 | show_more_text = 'Loading...' 28 | } 29 | 30 | if(work_items && item_num <= work_items.length){ 31 | load_more = ( 32 |
    33 | 36 |
    37 | ) 38 | } 39 | 40 | work_items = _.take(work_items, item_num) 41 | 42 | let articles_html = work_items.map(( work_item ) => { 43 | let date_obj = new Date(work_item.created) 44 | let created = (date_obj.getMonth()+1) + '/' + date_obj.getDate() + '/' + date_obj.getFullYear() 45 | return ( 46 |
    47 |
    48 |

    49 | { work_item.title } 50 |

    51 |

    Posted by Cosmic JS on { created }

    52 |
    53 |
    54 |
    55 | ) 56 | }) 57 | 58 | return ( 59 |
    60 |
    { articles_html }
    61 | { load_more } 62 |
    63 | ) 64 | } 65 | } -------------------------------------------------------------------------------- /components/Partials/WorkSingle.js: -------------------------------------------------------------------------------- 1 | // WorkSingle.js 2 | import React, { Component } from 'react' 3 | import _ from 'lodash' 4 | import { Link } from 'react-router' 5 | 6 | export default class WorkSingle extends Component { 7 | 8 | render(){ 9 | 10 | const work_item = this.props.work_item 11 | 12 | const style = { 13 | marginBottom: 20 14 | } 15 | 16 | return ( 17 |
    18 | << Back to Work List 19 |

    { work_item.title }

    20 |
    21 |
    22 | ) 23 | } 24 | } -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | // config.js 2 | export default { 3 | site: { 4 | title: 'React Universal Blog' 5 | }, 6 | bucket: { 7 | slug: process.env.COSMIC_BUCKET || 'react-universal-blog', 8 | media_url: 'https://cosmicjs.com/uploads', 9 | read_key: process.env.COSMIC_READ_KEY || '', 10 | write_key: process.env.COSMIC_WRITE_KEY || '' 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /dispatcher/AppDispatcher.js: -------------------------------------------------------------------------------- 1 | // AppDispatcher.js 2 | import { Dispatcher } from 'flux' 3 | import { getStore, getPageData, getMoreItems } from '../actions/actions' 4 | 5 | const AppDispatcher = new Dispatcher() 6 | 7 | // Register callback with AppDispatcher 8 | AppDispatcher.register((payload) => { 9 | 10 | let action = payload.action 11 | 12 | switch(action) { 13 | 14 | case 'get-app-store': 15 | getStore() 16 | break 17 | 18 | case 'get-page-data': 19 | getPageData(payload.page_slug, payload.post_slug) 20 | break 21 | 22 | case 'get-more-items': 23 | getMoreItems() 24 | break 25 | 26 | default: 27 | return true 28 | 29 | } 30 | 31 | return true 32 | 33 | }) 34 | 35 | export default AppDispatcher 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-universal-blog", 3 | "version": "1.0.0", 4 | "engines": { 5 | "node": "4.1.2", 6 | "npm": "3.5.2" 7 | }, 8 | "description": "", 9 | "main": "app-server.js", 10 | "dependencies": { 11 | "babel-cli": "^6.26.0", 12 | "babel-loader": "^7.1.2", 13 | "babel-preset-es2015": "^6.24.1", 14 | "babel-preset-es2017": "^6.24.1", 15 | "babel-preset-react": "^6.24.1", 16 | "babel-register": "^6.26.0", 17 | "cosmicjs": "^2.4.0", 18 | "flux": "^3.1.3", 19 | "history": "1.13.0", 20 | "hogan-express": "^0.5.2", 21 | "html-webpack-plugin": "^2.30.1", 22 | "path": "^0.12.7", 23 | "react": "^15.6.1", 24 | "react-dom": "^15.6.1", 25 | "react-router": "1.0.1", 26 | "webpack": "^3.5.6", 27 | "webpack-dev-server": "^2.7.1" 28 | }, 29 | "scripts": { 30 | "start": "npm run production", 31 | "production": "rm -rf public/index.html && NODE_ENV=production webpack -p && NODE_ENV=production babel-node app-server.js --presets es2015", 32 | "webpack-dev-server": "NODE_ENV=development PORT=8080 webpack-dev-server --content-base public/ --hot --inline --devtool inline-source-map --history-api-fallback", 33 | "development": "cp views/index.html public/index.html && NODE_ENV=development webpack && npm run webpack-dev-server" 34 | }, 35 | "author": "", 36 | "license": "ISC", 37 | "devDependencies": { 38 | "react-hot-loader": "^1.3.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/css/clean-blog.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Clean Blog v1.0.0 (http://startbootstrap.com) 3 | * Copyright 2014 Start Bootstrap 4 | * Licensed under Apache 2.0 (https://github.com/IronSummitMedia/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | 7 | body { 8 | font-family: 'Lora', 'Times New Roman', serif; 9 | font-size: 20px; 10 | color: #404040; 11 | } 12 | p { 13 | line-height: 1.5; 14 | margin: 30px 0; 15 | } 16 | p a { 17 | text-decoration: underline; 18 | } 19 | h1, 20 | h2, 21 | h3, 22 | h4, 23 | h5, 24 | h6 { 25 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | font-weight: 800; 27 | } 28 | a { 29 | color: #404040; 30 | } 31 | a:hover, 32 | a:focus { 33 | color: #0085a1; 34 | } 35 | a img:hover, 36 | a img:focus { 37 | cursor: zoom-in; 38 | } 39 | blockquote { 40 | color: #808080; 41 | font-style: italic; 42 | } 43 | hr.small { 44 | max-width: 100px; 45 | margin: 15px auto; 46 | border-width: 4px; 47 | border-color: white; 48 | } 49 | .navbar-custom { 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | width: 100%; 54 | z-index: 3; 55 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 56 | } 57 | .navbar-custom .navbar-brand { 58 | font-weight: 800; 59 | } 60 | .navbar-custom .nav li a { 61 | text-transform: uppercase; 62 | font-size: 12px; 63 | font-weight: 800; 64 | letter-spacing: 1px; 65 | } 66 | @media only screen and (min-width: 768px) { 67 | .navbar-custom { 68 | background: transparent; 69 | border-bottom: 1px solid transparent; 70 | } 71 | .navbar-custom .navbar-brand { 72 | color: white; 73 | padding: 20px; 74 | } 75 | .navbar-custom .navbar-brand:hover, 76 | .navbar-custom .navbar-brand:focus { 77 | color: rgba(255, 255, 255, 0.8); 78 | } 79 | .navbar-custom .nav li a { 80 | color: white; 81 | padding: 20px; 82 | } 83 | .navbar-custom .nav li a:hover, 84 | .navbar-custom .nav li a:focus { 85 | color: rgba(255, 255, 255, 0.8); 86 | } 87 | } 88 | @media only screen and (min-width: 1170px) { 89 | .navbar-custom { 90 | -webkit-transition: background-color 0.3s; 91 | -moz-transition: background-color 0.3s; 92 | transition: background-color 0.3s; 93 | /* Force Hardware Acceleration in WebKit */ 94 | -webkit-transform: translate3d(0, 0, 0); 95 | -moz-transform: translate3d(0, 0, 0); 96 | -ms-transform: translate3d(0, 0, 0); 97 | -o-transform: translate3d(0, 0, 0); 98 | transform: translate3d(0, 0, 0); 99 | -webkit-backface-visibility: hidden; 100 | backface-visibility: hidden; 101 | } 102 | .navbar-custom.is-fixed { 103 | /* when the user scrolls down, we hide the header right above the viewport */ 104 | position: fixed; 105 | top: -61px; 106 | background-color: rgba(255, 255, 255, 0.9); 107 | border-bottom: 1px solid #f2f2f2; 108 | -webkit-transition: -webkit-transform 0.3s; 109 | -moz-transition: -moz-transform 0.3s; 110 | transition: transform 0.3s; 111 | } 112 | .navbar-custom.is-fixed .navbar-brand { 113 | color: #404040; 114 | } 115 | .navbar-custom.is-fixed .navbar-brand:hover, 116 | .navbar-custom.is-fixed .navbar-brand:focus { 117 | color: #0085a1; 118 | } 119 | .navbar-custom.is-fixed .nav li a { 120 | color: #404040; 121 | } 122 | .navbar-custom.is-fixed .nav li a:hover, 123 | .navbar-custom.is-fixed .nav li a:focus { 124 | color: #0085a1; 125 | } 126 | .navbar-custom.is-visible { 127 | /* if the user changes the scrolling direction, we show the header */ 128 | -webkit-transform: translate3d(0, 100%, 0); 129 | -moz-transform: translate3d(0, 100%, 0); 130 | -ms-transform: translate3d(0, 100%, 0); 131 | -o-transform: translate3d(0, 100%, 0); 132 | transform: translate3d(0, 100%, 0); 133 | } 134 | } 135 | .intro-header { 136 | background-color: #808080; 137 | background: no-repeat center center; 138 | background-attachment: scroll; 139 | -webkit-background-size: cover; 140 | -moz-background-size: cover; 141 | background-size: cover; 142 | -o-background-size: cover; 143 | margin-bottom: 50px; 144 | } 145 | .intro-header .site-heading, 146 | .intro-header .post-heading, 147 | .intro-header .page-heading { 148 | padding: 100px 0 50px; 149 | color: white; 150 | } 151 | @media only screen and (min-width: 768px) { 152 | .intro-header .site-heading, 153 | .intro-header .post-heading, 154 | .intro-header .page-heading { 155 | padding: 150px 0; 156 | } 157 | } 158 | .intro-header .site-heading, 159 | .intro-header .page-heading { 160 | text-align: center; 161 | } 162 | .intro-header .site-heading h1, 163 | .intro-header .page-heading h1 { 164 | margin-top: 0; 165 | font-size: 50px; 166 | } 167 | .intro-header .site-heading .subheading, 168 | .intro-header .page-heading .subheading { 169 | font-size: 24px; 170 | line-height: 1.1; 171 | display: block; 172 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 173 | font-weight: 300; 174 | margin: 10px 0 0; 175 | } 176 | @media only screen and (min-width: 768px) { 177 | .intro-header .site-heading h1, 178 | .intro-header .page-heading h1 { 179 | font-size: 80px; 180 | } 181 | } 182 | .intro-header .post-heading h1 { 183 | font-size: 35px; 184 | } 185 | .intro-header .post-heading .subheading, 186 | .intro-header .post-heading .meta { 187 | line-height: 1.1; 188 | display: block; 189 | } 190 | .intro-header .post-heading .subheading { 191 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 192 | font-size: 24px; 193 | margin: 10px 0 30px; 194 | font-weight: 600; 195 | } 196 | .intro-header .post-heading .meta { 197 | font-family: 'Lora', 'Times New Roman', serif; 198 | font-style: italic; 199 | font-weight: 300; 200 | font-size: 20px; 201 | } 202 | .intro-header .post-heading .meta a { 203 | color: white; 204 | } 205 | @media only screen and (min-width: 768px) { 206 | .intro-header .post-heading h1 { 207 | font-size: 55px; 208 | } 209 | .intro-header .post-heading .subheading { 210 | font-size: 30px; 211 | } 212 | } 213 | .post-preview > a { 214 | color: #404040; 215 | } 216 | .post-preview > a:hover, 217 | .post-preview > a:focus { 218 | text-decoration: none; 219 | color: #0085a1; 220 | } 221 | .post-preview > a > .post-title { 222 | font-size: 30px; 223 | margin-top: 30px; 224 | margin-bottom: 10px; 225 | } 226 | .post-preview > a > .post-subtitle { 227 | margin: 0; 228 | font-weight: 300; 229 | margin-bottom: 10px; 230 | } 231 | .post-preview > .post-meta { 232 | color: #808080; 233 | font-size: 18px; 234 | font-style: italic; 235 | margin-top: 0; 236 | } 237 | .post-preview > .post-meta > a { 238 | text-decoration: none; 239 | color: #404040; 240 | } 241 | .post-preview > .post-meta > a:hover, 242 | .post-preview > .post-meta > a:focus { 243 | color: #0085a1; 244 | text-decoration: underline; 245 | } 246 | @media only screen and (min-width: 768px) { 247 | .post-preview > a > .post-title { 248 | font-size: 36px; 249 | } 250 | } 251 | .section-heading { 252 | font-size: 36px; 253 | margin-top: 60px; 254 | font-weight: 700; 255 | } 256 | .caption { 257 | text-align: center; 258 | font-size: 14px; 259 | padding: 10px; 260 | font-style: italic; 261 | margin: 0; 262 | display: block; 263 | border-bottom-right-radius: 5px; 264 | border-bottom-left-radius: 5px; 265 | } 266 | footer { 267 | padding: 50px 0 65px; 268 | } 269 | footer .list-inline { 270 | margin: 0; 271 | padding: 0; 272 | } 273 | footer .copyright { 274 | font-size: 14px; 275 | text-align: center; 276 | margin-bottom: 0; 277 | } 278 | .floating-label-form-group { 279 | font-size: 14px; 280 | position: relative; 281 | margin-bottom: 0; 282 | padding-bottom: 0.5em; 283 | border-bottom: 1px solid #eeeeee; 284 | } 285 | .floating-label-form-group input, 286 | .floating-label-form-group textarea { 287 | z-index: 1; 288 | position: relative; 289 | padding-right: 0; 290 | padding-left: 0; 291 | border: none; 292 | border-radius: 0; 293 | font-size: 1.5em; 294 | background: none; 295 | box-shadow: none !important; 296 | resize: none; 297 | } 298 | .floating-label-form-group label { 299 | display: block; 300 | z-index: 0; 301 | position: relative; 302 | top: 2em; 303 | margin: 0; 304 | font-size: 0.85em; 305 | line-height: 1.764705882em; 306 | vertical-align: middle; 307 | vertical-align: baseline; 308 | opacity: 0; 309 | -webkit-transition: top 0.3s ease,opacity 0.3s ease; 310 | -moz-transition: top 0.3s ease,opacity 0.3s ease; 311 | -ms-transition: top 0.3s ease,opacity 0.3s ease; 312 | transition: top 0.3s ease,opacity 0.3s ease; 313 | } 314 | .floating-label-form-group::not(:first-child) { 315 | padding-left: 14px; 316 | border-left: 1px solid #eeeeee; 317 | } 318 | .floating-label-form-group-with-value label { 319 | top: 0; 320 | opacity: 1; 321 | } 322 | .floating-label-form-group-with-focus label { 323 | color: #0085a1; 324 | } 325 | form .row:first-child .floating-label-form-group { 326 | border-top: 1px solid #eeeeee; 327 | } 328 | .btn { 329 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 330 | text-transform: uppercase; 331 | font-size: 14px; 332 | font-weight: 800; 333 | letter-spacing: 1px; 334 | border-radius: 0; 335 | padding: 15px 25px; 336 | } 337 | .btn-lg { 338 | font-size: 16px; 339 | padding: 25px 35px; 340 | } 341 | .btn-default:hover, 342 | .btn-default:focus { 343 | background-color: #0085a1; 344 | border: 1px solid #0085a1; 345 | color: white; 346 | } 347 | .pager { 348 | margin: 20px 0 0; 349 | } 350 | .pager li > a, 351 | .pager li > span { 352 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 353 | text-transform: uppercase; 354 | font-size: 14px; 355 | font-weight: 800; 356 | letter-spacing: 1px; 357 | padding: 15px 25px; 358 | background-color: white; 359 | border-radius: 0; 360 | } 361 | .pager li > a:hover, 362 | .pager li > a:focus { 363 | color: white; 364 | background-color: #0085a1; 365 | border: 1px solid #0085a1; 366 | } 367 | .pager .disabled > a, 368 | .pager .disabled > a:hover, 369 | .pager .disabled > a:focus, 370 | .pager .disabled > span { 371 | color: #808080; 372 | background-color: #404040; 373 | cursor: not-allowed; 374 | } 375 | ::-moz-selection { 376 | color: white; 377 | text-shadow: none; 378 | background: #0085a1; 379 | } 380 | ::selection { 381 | color: white; 382 | text-shadow: none; 383 | background: #0085a1; 384 | } 385 | img::selection { 386 | color: white; 387 | background: transparent; 388 | } 389 | img::-moz-selection { 390 | color: white; 391 | background: transparent; 392 | } 393 | body { 394 | webkit-tap-highlight-color: #0085a1; 395 | } 396 | -------------------------------------------------------------------------------- /public/css/clean-blog.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Clean Blog v1.0.0 (http://startbootstrap.com) 3 | * Copyright 2014 Start Bootstrap 4 | * Licensed under Apache 2.0 (https://github.com/IronSummitMedia/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | 7 | body{font-family:Lora,'Times New Roman',serif;font-size:20px;color:#404040}p{line-height:1.5;margin:30px 0}p a{text-decoration:underline}h1,h2,h3,h4,h5,h6{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:800}a{color:#404040}a:hover,a:focus{color:#0085a1}a img:hover,a img:focus{cursor:zoom-in}blockquote{color:gray;font-style:italic}hr.small{max-width:100px;margin:15px auto;border-width:4px;border-color:#fff}.navbar-custom{position:absolute;top:0;left:0;width:100%;z-index:3;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif}.navbar-custom .navbar-brand{font-weight:800}.navbar-custom .nav li a{text-transform:uppercase;font-size:12px;font-weight:800;letter-spacing:1px}@media only screen and (min-width:768px){.navbar-custom{background:0 0;border-bottom:1px solid transparent}.navbar-custom .navbar-brand{color:#fff;padding:20px}.navbar-custom .navbar-brand:hover,.navbar-custom .navbar-brand:focus{color:rgba(255,255,255,.8)}.navbar-custom .nav li a{color:#fff;padding:20px}.navbar-custom .nav li a:hover,.navbar-custom .nav li a:focus{color:rgba(255,255,255,.8)}}@media only screen and (min-width:1170px){.navbar-custom{-webkit-transition:background-color .3s;-moz-transition:background-color .3s;transition:background-color .3s;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0);-webkit-backface-visibility:hidden;backface-visibility:hidden}.navbar-custom.is-fixed{position:fixed;top:-61px;background-color:rgba(255,255,255,.9);border-bottom:1px solid #f2f2f2;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;transition:transform .3s}.navbar-custom.is-fixed .navbar-brand{color:#404040}.navbar-custom.is-fixed .navbar-brand:hover,.navbar-custom.is-fixed .navbar-brand:focus{color:#0085a1}.navbar-custom.is-fixed .nav li a{color:#404040}.navbar-custom.is-fixed .nav li a:hover,.navbar-custom.is-fixed .nav li a:focus{color:#0085a1}.navbar-custom.is-visible{-webkit-transform:translate3d(0,100%,0);-moz-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);-o-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.intro-header{background-color:gray;background:no-repeat center center;background-attachment:scroll;-webkit-background-size:cover;-moz-background-size:cover;background-size:cover;-o-background-size:cover;margin-bottom:50px}.intro-header .site-heading,.intro-header .post-heading,.intro-header .page-heading{padding:100px 0 50px;color:#fff}@media only screen and (min-width:768px){.intro-header .site-heading,.intro-header .post-heading,.intro-header .page-heading{padding:150px 0}}.intro-header .site-heading,.intro-header .page-heading{text-align:center}.intro-header .site-heading h1,.intro-header .page-heading h1{margin-top:0;font-size:50px}.intro-header .site-heading .subheading,.intro-header .page-heading .subheading{font-size:24px;line-height:1.1;display:block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:300;margin:10px 0 0}@media only screen and (min-width:768px){.intro-header .site-heading h1,.intro-header .page-heading h1{font-size:80px}}.intro-header .post-heading h1{font-size:35px}.intro-header .post-heading .subheading,.intro-header .post-heading .meta{line-height:1.1;display:block}.intro-header .post-heading .subheading{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:24px;margin:10px 0 30px;font-weight:600}.intro-header .post-heading .meta{font-family:Lora,'Times New Roman',serif;font-style:italic;font-weight:300;font-size:20px}.intro-header .post-heading .meta a{color:#fff}@media only screen and (min-width:768px){.intro-header .post-heading h1{font-size:55px}.intro-header .post-heading .subheading{font-size:30px}}.post-preview>a{color:#404040}.post-preview>a:hover,.post-preview>a:focus{text-decoration:none;color:#0085a1}.post-preview>a>.post-title{font-size:30px;margin-top:30px;margin-bottom:10px}.post-preview>a>.post-subtitle{margin:0;font-weight:300;margin-bottom:10px}.post-preview>.post-meta{color:gray;font-size:18px;font-style:italic;margin-top:0}.post-preview>.post-meta>a{text-decoration:none;color:#404040}.post-preview>.post-meta>a:hover,.post-preview>.post-meta>a:focus{color:#0085a1;text-decoration:underline}@media only screen and (min-width:768px){.post-preview>a>.post-title{font-size:36px}}.section-heading{font-size:36px;margin-top:60px;font-weight:700}.caption{text-align:center;font-size:14px;padding:10px;font-style:italic;margin:0;display:block;border-bottom-right-radius:5px;border-bottom-left-radius:5px}footer{padding:50px 0 65px}footer .list-inline{margin:0;padding:0}footer .copyright{font-size:14px;text-align:center;margin-bottom:0}.floating-label-form-group{font-size:14px;position:relative;margin-bottom:0;padding-bottom:.5em;border-bottom:1px solid #eee}.floating-label-form-group input,.floating-label-form-group textarea{z-index:1;position:relative;padding-right:0;padding-left:0;border:none;border-radius:0;font-size:1.5em;background:0 0;box-shadow:none!important;resize:none}.floating-label-form-group label{display:block;z-index:0;position:relative;top:2em;margin:0;font-size:.85em;line-height:1.764705882em;vertical-align:middle;vertical-align:baseline;opacity:0;-webkit-transition:top .3s ease,opacity .3s ease;-moz-transition:top .3s ease,opacity .3s ease;-ms-transition:top .3s ease,opacity .3s ease;transition:top .3s ease,opacity .3s ease}.floating-label-form-group::not(:first-child){padding-left:14px;border-left:1px solid #eee}.floating-label-form-group-with-value label{top:0;opacity:1}.floating-label-form-group-with-focus label{color:#0085a1}form .row:first-child .floating-label-form-group{border-top:1px solid #eee}.btn{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;text-transform:uppercase;font-size:14px;font-weight:800;letter-spacing:1px;border-radius:0;padding:15px 25px}.btn-lg{font-size:16px;padding:25px 35px}.btn-default:hover,.btn-default:focus{background-color:#0085a1;border:1px solid #0085a1;color:#fff}.pager{margin:20px 0 0}.pager li>a,.pager li>span{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;text-transform:uppercase;font-size:14px;font-weight:800;letter-spacing:1px;padding:15px 25px;background-color:#fff;border-radius:0}.pager li>a:hover,.pager li>a:focus{color:#fff;background-color:#0085a1;border:1px solid #0085a1}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:gray;background-color:#404040;cursor:not-allowed}::-moz-selection{color:#fff;text-shadow:none;background:#0085a1}::selection{color:#fff;text-shadow:none;background:#0085a1}img::selection{color:#fff;background:0 0}img::-moz-selection{color:#fff;background:0 0}body{webkit-tap-highlight-color:#0085a1} 8 | -------------------------------------------------------------------------------- /public/css/cosmic-custom.css: -------------------------------------------------------------------------------- 1 | .pointer{ 2 | cursor: pointer; 3 | } 4 | .intro-header{ 5 | height: 500px; 6 | background-size: cover; 7 | } 8 | #main-content img{ 9 | max-width: 100%; 10 | } 11 | @media(max-width : 768px){ 12 | .intro-header{ 13 | height: 280px; 14 | } 15 | } 16 | .sk-circle { 17 | margin: 100px auto; 18 | width: 40px; 19 | height: 40px; 20 | position: relative; 21 | } 22 | .sk-circle .sk-child { 23 | width: 100%; 24 | height: 100%; 25 | position: absolute; 26 | left: 0; 27 | top: 0; 28 | } 29 | .sk-circle .sk-child:before { 30 | content: ''; 31 | display: block; 32 | margin: 0 auto; 33 | width: 15%; 34 | height: 15%; 35 | background-color: #00afd7; 36 | border-radius: 100%; 37 | -webkit-animation: sk-circleBounceDelay 1.2s infinite ease-in-out both; 38 | animation: sk-circleBounceDelay 1.2s infinite ease-in-out both; 39 | } 40 | .sk-circle .sk-circle2 { 41 | -webkit-transform: rotate(30deg); 42 | -ms-transform: rotate(30deg); 43 | transform: rotate(30deg); } 44 | .sk-circle .sk-circle3 { 45 | -webkit-transform: rotate(60deg); 46 | -ms-transform: rotate(60deg); 47 | transform: rotate(60deg); } 48 | .sk-circle .sk-circle4 { 49 | -webkit-transform: rotate(90deg); 50 | -ms-transform: rotate(90deg); 51 | transform: rotate(90deg); } 52 | .sk-circle .sk-circle5 { 53 | -webkit-transform: rotate(120deg); 54 | -ms-transform: rotate(120deg); 55 | transform: rotate(120deg); } 56 | .sk-circle .sk-circle6 { 57 | -webkit-transform: rotate(150deg); 58 | -ms-transform: rotate(150deg); 59 | transform: rotate(150deg); } 60 | .sk-circle .sk-circle7 { 61 | -webkit-transform: rotate(180deg); 62 | -ms-transform: rotate(180deg); 63 | transform: rotate(180deg); } 64 | .sk-circle .sk-circle8 { 65 | -webkit-transform: rotate(210deg); 66 | -ms-transform: rotate(210deg); 67 | transform: rotate(210deg); } 68 | .sk-circle .sk-circle9 { 69 | -webkit-transform: rotate(240deg); 70 | -ms-transform: rotate(240deg); 71 | transform: rotate(240deg); } 72 | .sk-circle .sk-circle10 { 73 | -webkit-transform: rotate(270deg); 74 | -ms-transform: rotate(270deg); 75 | transform: rotate(270deg); } 76 | .sk-circle .sk-circle11 { 77 | -webkit-transform: rotate(300deg); 78 | -ms-transform: rotate(300deg); 79 | transform: rotate(300deg); } 80 | .sk-circle .sk-circle12 { 81 | -webkit-transform: rotate(330deg); 82 | -ms-transform: rotate(330deg); 83 | transform: rotate(330deg); } 84 | .sk-circle .sk-circle2:before { 85 | -webkit-animation-delay: -1.1s; 86 | animation-delay: -1.1s; } 87 | .sk-circle .sk-circle3:before { 88 | -webkit-animation-delay: -1s; 89 | animation-delay: -1s; } 90 | .sk-circle .sk-circle4:before { 91 | -webkit-animation-delay: -0.9s; 92 | animation-delay: -0.9s; } 93 | .sk-circle .sk-circle5:before { 94 | -webkit-animation-delay: -0.8s; 95 | animation-delay: -0.8s; } 96 | .sk-circle .sk-circle6:before { 97 | -webkit-animation-delay: -0.7s; 98 | animation-delay: -0.7s; } 99 | .sk-circle .sk-circle7:before { 100 | -webkit-animation-delay: -0.6s; 101 | animation-delay: -0.6s; } 102 | .sk-circle .sk-circle8:before { 103 | -webkit-animation-delay: -0.5s; 104 | animation-delay: -0.5s; } 105 | .sk-circle .sk-circle9:before { 106 | -webkit-animation-delay: -0.4s; 107 | animation-delay: -0.4s; } 108 | .sk-circle .sk-circle10:before { 109 | -webkit-animation-delay: -0.3s; 110 | animation-delay: -0.3s; } 111 | .sk-circle .sk-circle11:before { 112 | -webkit-animation-delay: -0.2s; 113 | animation-delay: -0.2s; } 114 | .sk-circle .sk-circle12:before { 115 | -webkit-animation-delay: -0.1s; 116 | animation-delay: -0.1s; } 117 | 118 | @-webkit-keyframes sk-circleBounceDelay { 119 | 0%, 80%, 100% { 120 | -webkit-transform: scale(0); 121 | transform: scale(0); 122 | } 40% { 123 | -webkit-transform: scale(1); 124 | transform: scale(1); 125 | } 126 | } 127 | 128 | @keyframes sk-circleBounceDelay { 129 | 0%, 80%, 100% { 130 | -webkit-transform: scale(0); 131 | transform: scale(0); 132 | } 40% { 133 | -webkit-transform: scale(1); 134 | transform: scale(1); 135 | } 136 | } -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-universal-blog/cd2e97e3e90d550f7a63b853ee36024bd15e18ce/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-universal-blog/cd2e97e3e90d550f7a63b853ee36024bd15e18ce/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-universal-blog/cd2e97e3e90d550f7a63b853ee36024bd15e18ce/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-universal-blog/cd2e97e3e90d550f7a63b853ee36024bd15e18ce/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /public/img/about-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-universal-blog/cd2e97e3e90d550f7a63b853ee36024bd15e18ce/public/img/about-bg.jpg -------------------------------------------------------------------------------- /public/img/contact-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-universal-blog/cd2e97e3e90d550f7a63b853ee36024bd15e18ce/public/img/contact-bg.jpg -------------------------------------------------------------------------------- /public/img/home-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-universal-blog/cd2e97e3e90d550f7a63b853ee36024bd15e18ce/public/img/home-bg.jpg -------------------------------------------------------------------------------- /public/img/post-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-universal-blog/cd2e97e3e90d550f7a63b853ee36024bd15e18ce/public/img/post-bg.jpg -------------------------------------------------------------------------------- /public/img/post-sample-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-universal-blog/cd2e97e3e90d550f7a63b853ee36024bd15e18ce/public/img/post-sample-image.jpg -------------------------------------------------------------------------------- /public/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-mp.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /public/js/clean-blog.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Clean Blog v1.0.0 (http://startbootstrap.com) 3 | * Copyright 2014 Start Bootstrap 4 | * Licensed under Apache 2.0 (https://github.com/IronSummitMedia/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | 7 | // Contact Form Scripts 8 | 9 | $(function() { 10 | 11 | $("input,textarea").jqBootstrapValidation({ 12 | preventSubmit: true, 13 | submitError: function($form, event, errors) { 14 | // additional error messages or events 15 | }, 16 | submitSuccess: function($form, event) { 17 | event.preventDefault(); // prevent default submit behaviour 18 | // get values from FORM 19 | var name = $("input#name").val(); 20 | var email = $("input#email").val(); 21 | var phone = $("input#phone").val(); 22 | var message = $("textarea#message").val(); 23 | var firstName = name; // For Success/Failure Message 24 | // Check for white space in name for Success/Fail message 25 | if (firstName.indexOf(' ') >= 0) { 26 | firstName = name.split(' ').slice(0, -1).join(' '); 27 | } 28 | $.ajax({ 29 | url: "././mail/contact_me.php", 30 | type: "POST", 31 | data: { 32 | name: name, 33 | phone: phone, 34 | email: email, 35 | message: message 36 | }, 37 | cache: false, 38 | success: function() { 39 | // Success message 40 | $('#success').html("
    "); 41 | $('#success > .alert-success').html(""); 43 | $('#success > .alert-success') 44 | .append("Your message has been sent. "); 45 | $('#success > .alert-success') 46 | .append('
    '); 47 | 48 | //clear all fields 49 | $('#contactForm').trigger("reset"); 50 | }, 51 | error: function() { 52 | // Fail message 53 | $('#success').html("
    "); 54 | $('#success > .alert-danger').html(""); 56 | $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!"); 57 | $('#success > .alert-danger').append('
    '); 58 | //clear all fields 59 | $('#contactForm').trigger("reset"); 60 | }, 61 | }) 62 | }, 63 | filter: function() { 64 | return $(this).is(":visible"); 65 | }, 66 | }); 67 | 68 | $("a[data-toggle=\"tab\"]").click(function(e) { 69 | e.preventDefault(); 70 | $(this).tab("show"); 71 | }); 72 | }); 73 | 74 | 75 | /*When clicking on Full hide fail/success boxes */ 76 | $('#name').focus(function() { 77 | $('#success').html(''); 78 | }); 79 | 80 | // jqBootstrapValidation 81 | // * A plugin for automating validation on Twitter Bootstrap formatted forms. 82 | // * 83 | // * v1.3.6 84 | // * 85 | // * License: MIT - see LICENSE file 86 | // * 87 | // * http://ReactiveRaven.github.com/jqBootstrapValidation/ 88 | 89 | 90 | (function( $ ){ 91 | 92 | var createdElements = []; 93 | 94 | var defaults = { 95 | options: { 96 | prependExistingHelpBlock: false, 97 | sniffHtml: true, // sniff for 'required', 'maxlength', etc 98 | preventSubmit: true, // stop the form submit event from firing if validation fails 99 | submitError: false, // function called if there is an error when trying to submit 100 | submitSuccess: false, // function called just before a successful submit event is sent to the server 101 | semanticallyStrict: false, // set to true to tidy up generated HTML output 102 | autoAdd: { 103 | helpBlocks: true 104 | }, 105 | filter: function () { 106 | // return $(this).is(":visible"); // only validate elements you can see 107 | return true; // validate everything 108 | } 109 | }, 110 | methods: { 111 | init : function( options ) { 112 | 113 | var settings = $.extend(true, {}, defaults); 114 | 115 | settings.options = $.extend(true, settings.options, options); 116 | 117 | var $siblingElements = this; 118 | 119 | var uniqueForms = $.unique( 120 | $siblingElements.map( function () { 121 | return $(this).parents("form")[0]; 122 | }).toArray() 123 | ); 124 | 125 | $(uniqueForms).bind("submit", function (e) { 126 | var $form = $(this); 127 | var warningsFound = 0; 128 | var $inputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter); 129 | $inputs.trigger("submit.validation").trigger("validationLostFocus.validation"); 130 | 131 | $inputs.each(function (i, el) { 132 | var $this = $(el), 133 | $controlGroup = $this.parents(".form-group").first(); 134 | if ( 135 | $controlGroup.hasClass("warning") 136 | ) { 137 | $controlGroup.removeClass("warning").addClass("error"); 138 | warningsFound++; 139 | } 140 | }); 141 | 142 | $inputs.trigger("validationLostFocus.validation"); 143 | 144 | if (warningsFound) { 145 | if (settings.options.preventSubmit) { 146 | e.preventDefault(); 147 | } 148 | $form.addClass("error"); 149 | if ($.isFunction(settings.options.submitError)) { 150 | settings.options.submitError($form, e, $inputs.jqBootstrapValidation("collectErrors", true)); 151 | } 152 | } else { 153 | $form.removeClass("error"); 154 | if ($.isFunction(settings.options.submitSuccess)) { 155 | settings.options.submitSuccess($form, e); 156 | } 157 | } 158 | }); 159 | 160 | return this.each(function(){ 161 | 162 | // Get references to everything we're interested in 163 | var $this = $(this), 164 | $controlGroup = $this.parents(".form-group").first(), 165 | $helpBlock = $controlGroup.find(".help-block").first(), 166 | $form = $this.parents("form").first(), 167 | validatorNames = []; 168 | 169 | // create message container if not exists 170 | if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) { 171 | $helpBlock = $('
    '); 172 | $controlGroup.find('.controls').append($helpBlock); 173 | createdElements.push($helpBlock[0]); 174 | } 175 | 176 | // ============================================================= 177 | // SNIFF HTML FOR VALIDATORS 178 | // ============================================================= 179 | 180 | // *snort sniff snuffle* 181 | 182 | if (settings.options.sniffHtml) { 183 | var message = ""; 184 | // --------------------------------------------------------- 185 | // PATTERN 186 | // --------------------------------------------------------- 187 | if ($this.attr("pattern") !== undefined) { 188 | message = "Not in the expected format"; 189 | if ($this.data("validationPatternMessage")) { 190 | message = $this.data("validationPatternMessage"); 191 | } 192 | $this.data("validationPatternMessage", message); 193 | $this.data("validationPatternRegex", $this.attr("pattern")); 194 | } 195 | // --------------------------------------------------------- 196 | // MAX 197 | // --------------------------------------------------------- 198 | if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) { 199 | var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax")); 200 | message = "Too high: Maximum of '" + max + "'"; 201 | if ($this.data("validationMaxMessage")) { 202 | message = $this.data("validationMaxMessage"); 203 | } 204 | $this.data("validationMaxMessage", message); 205 | $this.data("validationMaxMax", max); 206 | } 207 | // --------------------------------------------------------- 208 | // MIN 209 | // --------------------------------------------------------- 210 | if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) { 211 | var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin")); 212 | message = "Too low: Minimum of '" + min + "'"; 213 | if ($this.data("validationMinMessage")) { 214 | message = $this.data("validationMinMessage"); 215 | } 216 | $this.data("validationMinMessage", message); 217 | $this.data("validationMinMin", min); 218 | } 219 | // --------------------------------------------------------- 220 | // MAXLENGTH 221 | // --------------------------------------------------------- 222 | if ($this.attr("maxlength") !== undefined) { 223 | message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters"; 224 | if ($this.data("validationMaxlengthMessage")) { 225 | message = $this.data("validationMaxlengthMessage"); 226 | } 227 | $this.data("validationMaxlengthMessage", message); 228 | $this.data("validationMaxlengthMaxlength", $this.attr("maxlength")); 229 | } 230 | // --------------------------------------------------------- 231 | // MINLENGTH 232 | // --------------------------------------------------------- 233 | if ($this.attr("minlength") !== undefined) { 234 | message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters"; 235 | if ($this.data("validationMinlengthMessage")) { 236 | message = $this.data("validationMinlengthMessage"); 237 | } 238 | $this.data("validationMinlengthMessage", message); 239 | $this.data("validationMinlengthMinlength", $this.attr("minlength")); 240 | } 241 | // --------------------------------------------------------- 242 | // REQUIRED 243 | // --------------------------------------------------------- 244 | if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) { 245 | message = settings.builtInValidators.required.message; 246 | if ($this.data("validationRequiredMessage")) { 247 | message = $this.data("validationRequiredMessage"); 248 | } 249 | $this.data("validationRequiredMessage", message); 250 | } 251 | // --------------------------------------------------------- 252 | // NUMBER 253 | // --------------------------------------------------------- 254 | if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") { 255 | message = settings.builtInValidators.number.message; 256 | if ($this.data("validationNumberMessage")) { 257 | message = $this.data("validationNumberMessage"); 258 | } 259 | $this.data("validationNumberMessage", message); 260 | } 261 | // --------------------------------------------------------- 262 | // EMAIL 263 | // --------------------------------------------------------- 264 | if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") { 265 | message = "Not a valid email address"; 266 | if ($this.data("validationValidemailMessage")) { 267 | message = $this.data("validationValidemailMessage"); 268 | } else if ($this.data("validationEmailMessage")) { 269 | message = $this.data("validationEmailMessage"); 270 | } 271 | $this.data("validationValidemailMessage", message); 272 | } 273 | // --------------------------------------------------------- 274 | // MINCHECKED 275 | // --------------------------------------------------------- 276 | if ($this.attr("minchecked") !== undefined) { 277 | message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required"; 278 | if ($this.data("validationMincheckedMessage")) { 279 | message = $this.data("validationMincheckedMessage"); 280 | } 281 | $this.data("validationMincheckedMessage", message); 282 | $this.data("validationMincheckedMinchecked", $this.attr("minchecked")); 283 | } 284 | // --------------------------------------------------------- 285 | // MAXCHECKED 286 | // --------------------------------------------------------- 287 | if ($this.attr("maxchecked") !== undefined) { 288 | message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required"; 289 | if ($this.data("validationMaxcheckedMessage")) { 290 | message = $this.data("validationMaxcheckedMessage"); 291 | } 292 | $this.data("validationMaxcheckedMessage", message); 293 | $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked")); 294 | } 295 | } 296 | 297 | // ============================================================= 298 | // COLLECT VALIDATOR NAMES 299 | // ============================================================= 300 | 301 | // Get named validators 302 | if ($this.data("validation") !== undefined) { 303 | validatorNames = $this.data("validation").split(","); 304 | } 305 | 306 | // Get extra ones defined on the element's data attributes 307 | $.each($this.data(), function (i, el) { 308 | var parts = i.replace(/([A-Z])/g, ",$1").split(","); 309 | if (parts[0] === "validation" && parts[1]) { 310 | validatorNames.push(parts[1]); 311 | } 312 | }); 313 | 314 | // ============================================================= 315 | // NORMALISE VALIDATOR NAMES 316 | // ============================================================= 317 | 318 | var validatorNamesToInspect = validatorNames; 319 | var newValidatorNamesToInspect = []; 320 | 321 | do // repeatedly expand 'shortcut' validators into their real validators 322 | { 323 | // Uppercase only the first letter of each name 324 | $.each(validatorNames, function (i, el) { 325 | validatorNames[i] = formatValidatorName(el); 326 | }); 327 | 328 | // Remove duplicate validator names 329 | validatorNames = $.unique(validatorNames); 330 | 331 | // Pull out the new validator names from each shortcut 332 | newValidatorNamesToInspect = []; 333 | $.each(validatorNamesToInspect, function(i, el) { 334 | if ($this.data("validation" + el + "Shortcut") !== undefined) { 335 | // Are these custom validators? 336 | // Pull them out! 337 | $.each($this.data("validation" + el + "Shortcut").split(","), function(i2, el2) { 338 | newValidatorNamesToInspect.push(el2); 339 | }); 340 | } else if (settings.builtInValidators[el.toLowerCase()]) { 341 | // Is this a recognised built-in? 342 | // Pull it out! 343 | var validator = settings.builtInValidators[el.toLowerCase()]; 344 | if (validator.type.toLowerCase() === "shortcut") { 345 | $.each(validator.shortcut.split(","), function (i, el) { 346 | el = formatValidatorName(el); 347 | newValidatorNamesToInspect.push(el); 348 | validatorNames.push(el); 349 | }); 350 | } 351 | } 352 | }); 353 | 354 | validatorNamesToInspect = newValidatorNamesToInspect; 355 | 356 | } while (validatorNamesToInspect.length > 0) 357 | 358 | // ============================================================= 359 | // SET UP VALIDATOR ARRAYS 360 | // ============================================================= 361 | 362 | var validators = {}; 363 | 364 | $.each(validatorNames, function (i, el) { 365 | // Set up the 'override' message 366 | var message = $this.data("validation" + el + "Message"); 367 | var hasOverrideMessage = (message !== undefined); 368 | var foundValidator = false; 369 | message = 370 | ( 371 | message 372 | ? message 373 | : "'" + el + "' validation failed " 374 | ) 375 | ; 376 | 377 | $.each( 378 | settings.validatorTypes, 379 | function (validatorType, validatorTemplate) { 380 | if (validators[validatorType] === undefined) { 381 | validators[validatorType] = []; 382 | } 383 | if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) { 384 | validators[validatorType].push( 385 | $.extend( 386 | true, 387 | { 388 | name: formatValidatorName(validatorTemplate.name), 389 | message: message 390 | }, 391 | validatorTemplate.init($this, el) 392 | ) 393 | ); 394 | foundValidator = true; 395 | } 396 | } 397 | ); 398 | 399 | if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) { 400 | 401 | var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]); 402 | if (hasOverrideMessage) { 403 | validator.message = message; 404 | } 405 | var validatorType = validator.type.toLowerCase(); 406 | 407 | if (validatorType === "shortcut") { 408 | foundValidator = true; 409 | } else { 410 | $.each( 411 | settings.validatorTypes, 412 | function (validatorTemplateType, validatorTemplate) { 413 | if (validators[validatorTemplateType] === undefined) { 414 | validators[validatorTemplateType] = []; 415 | } 416 | if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) { 417 | $this.data("validation" + el + formatValidatorName(validatorTemplate.name), validator[validatorTemplate.name.toLowerCase()]); 418 | validators[validatorType].push( 419 | $.extend( 420 | validator, 421 | validatorTemplate.init($this, el) 422 | ) 423 | ); 424 | foundValidator = true; 425 | } 426 | } 427 | ); 428 | } 429 | } 430 | 431 | if (! foundValidator) { 432 | $.error("Cannot find validation info for '" + el + "'"); 433 | } 434 | }); 435 | 436 | // ============================================================= 437 | // STORE FALLBACK VALUES 438 | // ============================================================= 439 | 440 | $helpBlock.data( 441 | "original-contents", 442 | ( 443 | $helpBlock.data("original-contents") 444 | ? $helpBlock.data("original-contents") 445 | : $helpBlock.html() 446 | ) 447 | ); 448 | 449 | $helpBlock.data( 450 | "original-role", 451 | ( 452 | $helpBlock.data("original-role") 453 | ? $helpBlock.data("original-role") 454 | : $helpBlock.attr("role") 455 | ) 456 | ); 457 | 458 | $controlGroup.data( 459 | "original-classes", 460 | ( 461 | $controlGroup.data("original-clases") 462 | ? $controlGroup.data("original-classes") 463 | : $controlGroup.attr("class") 464 | ) 465 | ); 466 | 467 | $this.data( 468 | "original-aria-invalid", 469 | ( 470 | $this.data("original-aria-invalid") 471 | ? $this.data("original-aria-invalid") 472 | : $this.attr("aria-invalid") 473 | ) 474 | ); 475 | 476 | // ============================================================= 477 | // VALIDATION 478 | // ============================================================= 479 | 480 | $this.bind( 481 | "validation.validation", 482 | function (event, params) { 483 | 484 | var value = getValue($this); 485 | 486 | // Get a list of the errors to apply 487 | var errorsFound = []; 488 | 489 | $.each(validators, function (validatorType, validatorTypeArray) { 490 | if (value || value.length || (params && params.includeEmpty) || (!!settings.validatorTypes[validatorType].blockSubmit && params && !!params.submitting)) { 491 | $.each(validatorTypeArray, function (i, validator) { 492 | if (settings.validatorTypes[validatorType].validate($this, value, validator)) { 493 | errorsFound.push(validator.message); 494 | } 495 | }); 496 | } 497 | }); 498 | 499 | return errorsFound; 500 | } 501 | ); 502 | 503 | $this.bind( 504 | "getValidators.validation", 505 | function () { 506 | return validators; 507 | } 508 | ); 509 | 510 | // ============================================================= 511 | // WATCH FOR CHANGES 512 | // ============================================================= 513 | $this.bind( 514 | "submit.validation", 515 | function () { 516 | return $this.triggerHandler("change.validation", {submitting: true}); 517 | } 518 | ); 519 | $this.bind( 520 | [ 521 | "keyup", 522 | "focus", 523 | "blur", 524 | "click", 525 | "keydown", 526 | "keypress", 527 | "change" 528 | ].join(".validation ") + ".validation", 529 | function (e, params) { 530 | 531 | var value = getValue($this); 532 | 533 | var errorsFound = []; 534 | 535 | $controlGroup.find("input,textarea,select").each(function (i, el) { 536 | var oldCount = errorsFound.length; 537 | $.each($(el).triggerHandler("validation.validation", params), function (j, message) { 538 | errorsFound.push(message); 539 | }); 540 | if (errorsFound.length > oldCount) { 541 | $(el).attr("aria-invalid", "true"); 542 | } else { 543 | var original = $this.data("original-aria-invalid"); 544 | $(el).attr("aria-invalid", (original !== undefined ? original : false)); 545 | } 546 | }); 547 | 548 | $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation"); 549 | 550 | errorsFound = $.unique(errorsFound.sort()); 551 | 552 | // Were there any errors? 553 | if (errorsFound.length) { 554 | // Better flag it up as a warning. 555 | $controlGroup.removeClass("success error").addClass("warning"); 556 | 557 | // How many errors did we find? 558 | if (settings.options.semanticallyStrict && errorsFound.length === 1) { 559 | // Only one? Being strict? Just output it. 560 | $helpBlock.html(errorsFound[0] + 561 | ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" )); 562 | } else { 563 | // Multiple? Being sloppy? Glue them together into an UL. 564 | $helpBlock.html("
    • " + errorsFound.join("
    • ") + "
    " + 565 | ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" )); 566 | } 567 | } else { 568 | $controlGroup.removeClass("warning error success"); 569 | if (value.length > 0) { 570 | $controlGroup.addClass("success"); 571 | } 572 | $helpBlock.html($helpBlock.data("original-contents")); 573 | } 574 | 575 | if (e.type === "blur") { 576 | $controlGroup.removeClass("success"); 577 | } 578 | } 579 | ); 580 | $this.bind("validationLostFocus.validation", function () { 581 | $controlGroup.removeClass("success"); 582 | }); 583 | }); 584 | }, 585 | destroy : function( ) { 586 | 587 | return this.each( 588 | function() { 589 | 590 | var 591 | $this = $(this), 592 | $controlGroup = $this.parents(".form-group").first(), 593 | $helpBlock = $controlGroup.find(".help-block").first(); 594 | 595 | // remove our events 596 | $this.unbind('.validation'); // events are namespaced. 597 | // reset help text 598 | $helpBlock.html($helpBlock.data("original-contents")); 599 | // reset classes 600 | $controlGroup.attr("class", $controlGroup.data("original-classes")); 601 | // reset aria 602 | $this.attr("aria-invalid", $this.data("original-aria-invalid")); 603 | // reset role 604 | $helpBlock.attr("role", $this.data("original-role")); 605 | // remove all elements we created 606 | if (createdElements.indexOf($helpBlock[0]) > -1) { 607 | $helpBlock.remove(); 608 | } 609 | 610 | } 611 | ); 612 | 613 | }, 614 | collectErrors : function(includeEmpty) { 615 | 616 | var errorMessages = {}; 617 | this.each(function (i, el) { 618 | var $el = $(el); 619 | var name = $el.attr("name"); 620 | var errors = $el.triggerHandler("validation.validation", {includeEmpty: true}); 621 | errorMessages[name] = $.extend(true, errors, errorMessages[name]); 622 | }); 623 | 624 | $.each(errorMessages, function (i, el) { 625 | if (el.length === 0) { 626 | delete errorMessages[i]; 627 | } 628 | }); 629 | 630 | return errorMessages; 631 | 632 | }, 633 | hasErrors: function() { 634 | 635 | var errorMessages = []; 636 | 637 | this.each(function (i, el) { 638 | errorMessages = errorMessages.concat( 639 | $(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", {submitting: true}) : [] 640 | ); 641 | }); 642 | 643 | return (errorMessages.length > 0); 644 | }, 645 | override : function (newDefaults) { 646 | defaults = $.extend(true, defaults, newDefaults); 647 | } 648 | }, 649 | validatorTypes: { 650 | callback: { 651 | name: "callback", 652 | init: function ($this, name) { 653 | return { 654 | validatorName: name, 655 | callback: $this.data("validation" + name + "Callback"), 656 | lastValue: $this.val(), 657 | lastValid: true, 658 | lastFinished: true 659 | }; 660 | }, 661 | validate: function ($this, value, validator) { 662 | if (validator.lastValue === value && validator.lastFinished) { 663 | return !validator.lastValid; 664 | } 665 | 666 | if (validator.lastFinished === true) 667 | { 668 | validator.lastValue = value; 669 | validator.lastValid = true; 670 | validator.lastFinished = false; 671 | 672 | var rrjqbvValidator = validator; 673 | var rrjqbvThis = $this; 674 | executeFunctionByName( 675 | validator.callback, 676 | window, 677 | $this, 678 | value, 679 | function (data) { 680 | if (rrjqbvValidator.lastValue === data.value) { 681 | rrjqbvValidator.lastValid = data.valid; 682 | if (data.message) { 683 | rrjqbvValidator.message = data.message; 684 | } 685 | rrjqbvValidator.lastFinished = true; 686 | rrjqbvThis.data("validation" + rrjqbvValidator.validatorName + "Message", rrjqbvValidator.message); 687 | // Timeout is set to avoid problems with the events being considered 'already fired' 688 | setTimeout(function () { 689 | rrjqbvThis.trigger("change.validation"); 690 | }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst 691 | } 692 | } 693 | ); 694 | } 695 | 696 | return false; 697 | 698 | } 699 | }, 700 | ajax: { 701 | name: "ajax", 702 | init: function ($this, name) { 703 | return { 704 | validatorName: name, 705 | url: $this.data("validation" + name + "Ajax"), 706 | lastValue: $this.val(), 707 | lastValid: true, 708 | lastFinished: true 709 | }; 710 | }, 711 | validate: function ($this, value, validator) { 712 | if (""+validator.lastValue === ""+value && validator.lastFinished === true) { 713 | return validator.lastValid === false; 714 | } 715 | 716 | if (validator.lastFinished === true) 717 | { 718 | validator.lastValue = value; 719 | validator.lastValid = true; 720 | validator.lastFinished = false; 721 | $.ajax({ 722 | url: validator.url, 723 | data: "value=" + value + "&field=" + $this.attr("name"), 724 | dataType: "json", 725 | success: function (data) { 726 | if (""+validator.lastValue === ""+data.value) { 727 | validator.lastValid = !!(data.valid); 728 | if (data.message) { 729 | validator.message = data.message; 730 | } 731 | validator.lastFinished = true; 732 | $this.data("validation" + validator.validatorName + "Message", validator.message); 733 | // Timeout is set to avoid problems with the events being considered 'already fired' 734 | setTimeout(function () { 735 | $this.trigger("change.validation"); 736 | }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst 737 | } 738 | }, 739 | failure: function () { 740 | validator.lastValid = true; 741 | validator.message = "ajax call failed"; 742 | validator.lastFinished = true; 743 | $this.data("validation" + validator.validatorName + "Message", validator.message); 744 | // Timeout is set to avoid problems with the events being considered 'already fired' 745 | setTimeout(function () { 746 | $this.trigger("change.validation"); 747 | }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst 748 | } 749 | }); 750 | } 751 | 752 | return false; 753 | 754 | } 755 | }, 756 | regex: { 757 | name: "regex", 758 | init: function ($this, name) { 759 | return {regex: regexFromString($this.data("validation" + name + "Regex"))}; 760 | }, 761 | validate: function ($this, value, validator) { 762 | return (!validator.regex.test(value) && ! validator.negative) 763 | || (validator.regex.test(value) && validator.negative); 764 | } 765 | }, 766 | required: { 767 | name: "required", 768 | init: function ($this, name) { 769 | return {}; 770 | }, 771 | validate: function ($this, value, validator) { 772 | return !!(value.length === 0 && ! validator.negative) 773 | || !!(value.length > 0 && validator.negative); 774 | }, 775 | blockSubmit: true 776 | }, 777 | match: { 778 | name: "match", 779 | init: function ($this, name) { 780 | var element = $this.parents("form").first().find("[name=\"" + $this.data("validation" + name + "Match") + "\"]").first(); 781 | element.bind("validation.validation", function () { 782 | $this.trigger("change.validation", {submitting: true}); 783 | }); 784 | return {"element": element}; 785 | }, 786 | validate: function ($this, value, validator) { 787 | return (value !== validator.element.val() && ! validator.negative) 788 | || (value === validator.element.val() && validator.negative); 789 | }, 790 | blockSubmit: true 791 | }, 792 | max: { 793 | name: "max", 794 | init: function ($this, name) { 795 | return {max: $this.data("validation" + name + "Max")}; 796 | }, 797 | validate: function ($this, value, validator) { 798 | return (parseFloat(value, 10) > parseFloat(validator.max, 10) && ! validator.negative) 799 | || (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative); 800 | } 801 | }, 802 | min: { 803 | name: "min", 804 | init: function ($this, name) { 805 | return {min: $this.data("validation" + name + "Min")}; 806 | }, 807 | validate: function ($this, value, validator) { 808 | return (parseFloat(value) < parseFloat(validator.min) && ! validator.negative) 809 | || (parseFloat(value) >= parseFloat(validator.min) && validator.negative); 810 | } 811 | }, 812 | maxlength: { 813 | name: "maxlength", 814 | init: function ($this, name) { 815 | return {maxlength: $this.data("validation" + name + "Maxlength")}; 816 | }, 817 | validate: function ($this, value, validator) { 818 | return ((value.length > validator.maxlength) && ! validator.negative) 819 | || ((value.length <= validator.maxlength) && validator.negative); 820 | } 821 | }, 822 | minlength: { 823 | name: "minlength", 824 | init: function ($this, name) { 825 | return {minlength: $this.data("validation" + name + "Minlength")}; 826 | }, 827 | validate: function ($this, value, validator) { 828 | return ((value.length < validator.minlength) && ! validator.negative) 829 | || ((value.length >= validator.minlength) && validator.negative); 830 | } 831 | }, 832 | maxchecked: { 833 | name: "maxchecked", 834 | init: function ($this, name) { 835 | var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); 836 | elements.bind("click.validation", function () { 837 | $this.trigger("change.validation", {includeEmpty: true}); 838 | }); 839 | return {maxchecked: $this.data("validation" + name + "Maxchecked"), elements: elements}; 840 | }, 841 | validate: function ($this, value, validator) { 842 | return (validator.elements.filter(":checked").length > validator.maxchecked && ! validator.negative) 843 | || (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative); 844 | }, 845 | blockSubmit: true 846 | }, 847 | minchecked: { 848 | name: "minchecked", 849 | init: function ($this, name) { 850 | var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); 851 | elements.bind("click.validation", function () { 852 | $this.trigger("change.validation", {includeEmpty: true}); 853 | }); 854 | return {minchecked: $this.data("validation" + name + "Minchecked"), elements: elements}; 855 | }, 856 | validate: function ($this, value, validator) { 857 | return (validator.elements.filter(":checked").length < validator.minchecked && ! validator.negative) 858 | || (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative); 859 | }, 860 | blockSubmit: true 861 | } 862 | }, 863 | builtInValidators: { 864 | email: { 865 | name: "Email", 866 | type: "shortcut", 867 | shortcut: "validemail" 868 | }, 869 | validemail: { 870 | name: "Validemail", 871 | type: "regex", 872 | regex: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\.[A-Za-z]{2,4}", 873 | message: "Not a valid email address" 874 | }, 875 | passwordagain: { 876 | name: "Passwordagain", 877 | type: "match", 878 | match: "password", 879 | message: "Does not match the given password" 880 | }, 881 | positive: { 882 | name: "Positive", 883 | type: "shortcut", 884 | shortcut: "number,positivenumber" 885 | }, 886 | negative: { 887 | name: "Negative", 888 | type: "shortcut", 889 | shortcut: "number,negativenumber" 890 | }, 891 | number: { 892 | name: "Number", 893 | type: "regex", 894 | regex: "([+-]?\\\d+(\\\.\\\d*)?([eE][+-]?[0-9]+)?)?", 895 | message: "Must be a number" 896 | }, 897 | integer: { 898 | name: "Integer", 899 | type: "regex", 900 | regex: "[+-]?\\\d+", 901 | message: "No decimal places allowed" 902 | }, 903 | positivenumber: { 904 | name: "Positivenumber", 905 | type: "min", 906 | min: 0, 907 | message: "Must be a positive number" 908 | }, 909 | negativenumber: { 910 | name: "Negativenumber", 911 | type: "max", 912 | max: 0, 913 | message: "Must be a negative number" 914 | }, 915 | required: { 916 | name: "Required", 917 | type: "required", 918 | message: "This is required" 919 | }, 920 | checkone: { 921 | name: "Checkone", 922 | type: "minchecked", 923 | minchecked: 1, 924 | message: "Check at least one option" 925 | } 926 | } 927 | }; 928 | 929 | var formatValidatorName = function (name) { 930 | return name 931 | .toLowerCase() 932 | .replace( 933 | /(^|\s)([a-z])/g , 934 | function(m,p1,p2) { 935 | return p1+p2.toUpperCase(); 936 | } 937 | ) 938 | ; 939 | }; 940 | 941 | var getValue = function ($this) { 942 | // Extract the value we're talking about 943 | var value = $this.val(); 944 | var type = $this.attr("type"); 945 | if (type === "checkbox") { 946 | value = ($this.is(":checked") ? value : ""); 947 | } 948 | if (type === "radio") { 949 | value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? value : ""); 950 | } 951 | return value; 952 | }; 953 | 954 | function regexFromString(inputstring) { 955 | return new RegExp("^" + inputstring + "$"); 956 | } 957 | 958 | /** 959 | * Thanks to Jason Bunting via StackOverflow.com 960 | * 961 | * http://stackoverflow.com/questions/359788/how-to-execute-a-javascript-function-when-i-have-its-name-as-a-string#answer-359910 962 | * Short link: http://tinyurl.com/executeFunctionByName 963 | **/ 964 | function executeFunctionByName(functionName, context /*, args*/) { 965 | var args = Array.prototype.slice.call(arguments).splice(2); 966 | var namespaces = functionName.split("."); 967 | var func = namespaces.pop(); 968 | for(var i = 0; i < namespaces.length; i++) { 969 | context = context[namespaces[i]]; 970 | } 971 | return context[func].apply(this, args); 972 | } 973 | 974 | $.fn.jqBootstrapValidation = function( method ) { 975 | 976 | if ( defaults.methods[method] ) { 977 | return defaults.methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); 978 | } else if ( typeof method === 'object' || ! method ) { 979 | return defaults.methods.init.apply( this, arguments ); 980 | } else { 981 | $.error( 'Method ' + method + ' does not exist on jQuery.jqBootstrapValidation' ); 982 | return null; 983 | } 984 | 985 | }; 986 | 987 | $.jqBootstrapValidation = function (options) { 988 | $(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this,arguments); 989 | }; 990 | 991 | })( jQuery ); 992 | 993 | // Floating label headings for the contact form 994 | $(function() { 995 | $("body").on("input propertychange", ".floating-label-form-group", function(e) { 996 | $(this).toggleClass("floating-label-form-group-with-value", !!$(e.target).val()); 997 | }).on("focus", ".floating-label-form-group", function() { 998 | $(this).addClass("floating-label-form-group-with-focus"); 999 | }).on("blur", ".floating-label-form-group", function() { 1000 | $(this).removeClass("floating-label-form-group-with-focus"); 1001 | }); 1002 | }); 1003 | 1004 | // Navigation Scripts to Show Header on Scroll-Up 1005 | jQuery(document).ready(function($) { 1006 | var MQL = 1170; 1007 | 1008 | //primary navigation slide-in effect 1009 | if ($(window).width() > MQL) { 1010 | var headerHeight = $('.navbar-custom').height(); 1011 | $(window).on('scroll', { 1012 | previousTop: 0 1013 | }, 1014 | function() { 1015 | var currentTop = $(window).scrollTop(); 1016 | //check if user is scrolling up 1017 | if (currentTop < this.previousTop) { 1018 | //if scrolling up... 1019 | if (currentTop > 0 && $('.navbar-custom').hasClass('is-fixed')) { 1020 | $('.navbar-custom').addClass('is-visible'); 1021 | } else { 1022 | $('.navbar-custom').removeClass('is-visible is-fixed'); 1023 | } 1024 | } else { 1025 | //if scrolling down... 1026 | $('.navbar-custom').removeClass('is-visible'); 1027 | if (currentTop > headerHeight && !$('.navbar-custom').hasClass('is-fixed')) $('.navbar-custom').addClass('is-fixed'); 1028 | } 1029 | this.previousTop = currentTop; 1030 | }); 1031 | } 1032 | }); 1033 | -------------------------------------------------------------------------------- /public/js/clean-blog.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Clean Blog v1.0.0 (http://startbootstrap.com) 3 | * Copyright 2014 Start Bootstrap 4 | * Licensed under Apache 2.0 (https://github.com/IronSummitMedia/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | 7 | $(function(){$("input,textarea").jqBootstrapValidation({preventSubmit:!0,submitError:function(){},submitSuccess:function(a,b){b.preventDefault();var c=$("input#name").val(),d=$("input#email").val(),e=$("input#phone").val(),f=$("textarea#message").val(),g=c;g.indexOf(" ")>=0&&(g=c.split(" ").slice(0,-1).join(" ")),$.ajax({url:"././mail/contact_me.php",type:"POST",data:{name:c,phone:e,email:d,message:f},cache:!1,success:function(){$("#success").html("
    "),$("#success > .alert-success").html(""),$("#success > .alert-success").append("Your message has been sent. "),$("#success > .alert-success").append("
    "),$("#contactForm").trigger("reset")},error:function(){$("#success").html("
    "),$("#success > .alert-danger").html(""),$("#success > .alert-danger").append("Sorry "+g+", it seems that my mail server is not responding. Please try again later!"),$("#success > .alert-danger").append("
    "),$("#contactForm").trigger("reset")}})},filter:function(){return $(this).is(":visible")}}),$('a[data-toggle="tab"]').click(function(a){a.preventDefault(),$(this).tab("show")})}),$("#name").focus(function(){$("#success").html("")}),function(a){function b(a){return new RegExp("^"+a+"$")}function c(a,b){for(var c=Array.prototype.slice.call(arguments).splice(2),d=a.split("."),e=d.pop(),f=0;f'),e.find(".controls").append(h),d.push(h[0])),c.options.sniffHtml){var k="";if(void 0!==b.attr("pattern")&&(k="Not in the expected format",b.data("validationPatternMessage")&&(k=b.data("validationPatternMessage")),b.data("validationPatternMessage",k),b.data("validationPatternRegex",b.attr("pattern"))),void 0!==b.attr("max")||void 0!==b.attr("aria-valuemax")){var l=b.attr(void 0!==b.attr("max")?"max":"aria-valuemax");k="Too high: Maximum of '"+l+"'",b.data("validationMaxMessage")&&(k=b.data("validationMaxMessage")),b.data("validationMaxMessage",k),b.data("validationMaxMax",l)}if(void 0!==b.attr("min")||void 0!==b.attr("aria-valuemin")){var m=b.attr(void 0!==b.attr("min")?"min":"aria-valuemin");k="Too low: Minimum of '"+m+"'",b.data("validationMinMessage")&&(k=b.data("validationMinMessage")),b.data("validationMinMessage",k),b.data("validationMinMin",m)}void 0!==b.attr("maxlength")&&(k="Too long: Maximum of '"+b.attr("maxlength")+"' characters",b.data("validationMaxlengthMessage")&&(k=b.data("validationMaxlengthMessage")),b.data("validationMaxlengthMessage",k),b.data("validationMaxlengthMaxlength",b.attr("maxlength"))),void 0!==b.attr("minlength")&&(k="Too short: Minimum of '"+b.attr("minlength")+"' characters",b.data("validationMinlengthMessage")&&(k=b.data("validationMinlengthMessage")),b.data("validationMinlengthMessage",k),b.data("validationMinlengthMinlength",b.attr("minlength"))),(void 0!==b.attr("required")||void 0!==b.attr("aria-required"))&&(k=c.builtInValidators.required.message,b.data("validationRequiredMessage")&&(k=b.data("validationRequiredMessage")),b.data("validationRequiredMessage",k)),void 0!==b.attr("type")&&"number"===b.attr("type").toLowerCase()&&(k=c.builtInValidators.number.message,b.data("validationNumberMessage")&&(k=b.data("validationNumberMessage")),b.data("validationNumberMessage",k)),void 0!==b.attr("type")&&"email"===b.attr("type").toLowerCase()&&(k="Not a valid email address",b.data("validationValidemailMessage")?k=b.data("validationValidemailMessage"):b.data("validationEmailMessage")&&(k=b.data("validationEmailMessage")),b.data("validationValidemailMessage",k)),void 0!==b.attr("minchecked")&&(k="Not enough options checked; Minimum of '"+b.attr("minchecked")+"' required",b.data("validationMincheckedMessage")&&(k=b.data("validationMincheckedMessage")),b.data("validationMincheckedMessage",k),b.data("validationMincheckedMinchecked",b.attr("minchecked"))),void 0!==b.attr("maxchecked")&&(k="Too many options checked; Maximum of '"+b.attr("maxchecked")+"' required",b.data("validationMaxcheckedMessage")&&(k=b.data("validationMaxcheckedMessage")),b.data("validationMaxcheckedMessage",k),b.data("validationMaxcheckedMaxchecked",b.attr("maxchecked")))}void 0!==b.data("validation")&&(j=b.data("validation").split(",")),a.each(b.data(),function(a){var b=a.replace(/([A-Z])/g,",$1").split(",");"validation"===b[0]&&b[1]&&j.push(b[1])});var n=j,o=[];do a.each(j,function(a,b){j[a]=f(b)}),j=a.unique(j),o=[],a.each(n,function(d,e){if(void 0!==b.data("validation"+e+"Shortcut"))a.each(b.data("validation"+e+"Shortcut").split(","),function(a,b){o.push(b)});else if(c.builtInValidators[e.toLowerCase()]){var g=c.builtInValidators[e.toLowerCase()];"shortcut"===g.type.toLowerCase()&&a.each(g.shortcut.split(","),function(a,b){b=f(b),o.push(b),j.push(b)})}}),n=o;while(n.length>0);var p={};a.each(j,function(d,e){var g=b.data("validation"+e+"Message"),h=void 0!==g,i=!1;if(g=g?g:"'"+e+"' validation failed ",a.each(c.validatorTypes,function(c,d){void 0===p[c]&&(p[c]=[]),i||void 0===b.data("validation"+e+f(d.name))||(p[c].push(a.extend(!0,{name:f(d.name),message:g},d.init(b,e))),i=!0)}),!i&&c.builtInValidators[e.toLowerCase()]){var j=a.extend(!0,{},c.builtInValidators[e.toLowerCase()]);h&&(j.message=g);var k=j.type.toLowerCase();"shortcut"===k?i=!0:a.each(c.validatorTypes,function(c,d){void 0===p[c]&&(p[c]=[]),i||k!==c.toLowerCase()||(b.data("validation"+e+f(d.name),j[d.name.toLowerCase()]),p[k].push(a.extend(j,d.init(b,e))),i=!0)})}i||a.error("Cannot find validation info for '"+e+"'")}),h.data("original-contents",h.data("original-contents")?h.data("original-contents"):h.html()),h.data("original-role",h.data("original-role")?h.data("original-role"):h.attr("role")),e.data("original-classes",e.data("original-clases")?e.data("original-classes"):e.attr("class")),b.data("original-aria-invalid",b.data("original-aria-invalid")?b.data("original-aria-invalid"):b.attr("aria-invalid")),b.bind("validation.validation",function(d,e){var f=g(b),h=[];return a.each(p,function(d,g){(f||f.length||e&&e.includeEmpty||c.validatorTypes[d].blockSubmit&&e&&e.submitting)&&a.each(g,function(a,e){c.validatorTypes[d].validate(b,f,e)&&h.push(e.message)})}),h}),b.bind("getValidators.validation",function(){return p}),b.bind("submit.validation",function(){return b.triggerHandler("change.validation",{submitting:!0})}),b.bind(["keyup","focus","blur","click","keydown","keypress","change"].join(".validation ")+".validation",function(d,f){var j=g(b),k=[];e.find("input,textarea,select").each(function(c,d){var e=k.length;if(a.each(a(d).triggerHandler("validation.validation",f),function(a,b){k.push(b)}),k.length>e)a(d).attr("aria-invalid","true");else{var g=b.data("original-aria-invalid");a(d).attr("aria-invalid",void 0!==g?g:!1)}}),i.find("input,select,textarea").not(b).not('[name="'+b.attr("name")+'"]').trigger("validationLostFocus.validation"),k=a.unique(k.sort()),k.length?(e.removeClass("success error").addClass("warning"),h.html(c.options.semanticallyStrict&&1===k.length?k[0]+(c.options.prependExistingHelpBlock?h.data("original-contents"):""):'
    • '+k.join("
    • ")+"
    "+(c.options.prependExistingHelpBlock?h.data("original-contents"):""))):(e.removeClass("warning error success"),j.length>0&&e.addClass("success"),h.html(h.data("original-contents"))),"blur"===d.type&&e.removeClass("success")}),b.bind("validationLostFocus.validation",function(){e.removeClass("success")})})},destroy:function(){return this.each(function(){var b=a(this),c=b.parents(".form-group").first(),e=c.find(".help-block").first();b.unbind(".validation"),e.html(e.data("original-contents")),c.attr("class",c.data("original-classes")),b.attr("aria-invalid",b.data("original-aria-invalid")),e.attr("role",b.data("original-role")),d.indexOf(e[0])>-1&&e.remove()})},collectErrors:function(){var b={};return this.each(function(c,d){var e=a(d),f=e.attr("name"),g=e.triggerHandler("validation.validation",{includeEmpty:!0});b[f]=a.extend(!0,g,b[f])}),a.each(b,function(a,c){0===c.length&&delete b[a]}),b},hasErrors:function(){var b=[];return this.each(function(c,d){b=b.concat(a(d).triggerHandler("getValidators.validation")?a(d).triggerHandler("validation.validation",{submitting:!0}):[])}),b.length>0},override:function(b){e=a.extend(!0,e,b)}},validatorTypes:{callback:{name:"callback",init:function(a,b){return{validatorName:b,callback:a.data("validation"+b+"Callback"),lastValue:a.val(),lastValid:!0,lastFinished:!0}},validate:function(a,b,d){if(d.lastValue===b&&d.lastFinished)return!d.lastValid;if(d.lastFinished===!0){d.lastValue=b,d.lastValid=!0,d.lastFinished=!1;var e=d,f=a;c(d.callback,window,a,b,function(a){e.lastValue===a.value&&(e.lastValid=a.valid,a.message&&(e.message=a.message),e.lastFinished=!0,f.data("validation"+e.validatorName+"Message",e.message),setTimeout(function(){f.trigger("change.validation")},1))})}return!1}},ajax:{name:"ajax",init:function(a,b){return{validatorName:b,url:a.data("validation"+b+"Ajax"),lastValue:a.val(),lastValid:!0,lastFinished:!0}},validate:function(b,c,d){return""+d.lastValue==""+c&&d.lastFinished===!0?d.lastValid===!1:(d.lastFinished===!0&&(d.lastValue=c,d.lastValid=!0,d.lastFinished=!1,a.ajax({url:d.url,data:"value="+c+"&field="+b.attr("name"),dataType:"json",success:function(a){""+d.lastValue==""+a.value&&(d.lastValid=!!a.valid,a.message&&(d.message=a.message),d.lastFinished=!0,b.data("validation"+d.validatorName+"Message",d.message),setTimeout(function(){b.trigger("change.validation")},1))},failure:function(){d.lastValid=!0,d.message="ajax call failed",d.lastFinished=!0,b.data("validation"+d.validatorName+"Message",d.message),setTimeout(function(){b.trigger("change.validation")},1)}})),!1)}},regex:{name:"regex",init:function(a,c){return{regex:b(a.data("validation"+c+"Regex"))}},validate:function(a,b,c){return!c.regex.test(b)&&!c.negative||c.regex.test(b)&&c.negative}},required:{name:"required",init:function(){return{}},validate:function(a,b,c){return!(0!==b.length||c.negative)||!!(b.length>0&&c.negative)},blockSubmit:!0},match:{name:"match",init:function(a,b){var c=a.parents("form").first().find('[name="'+a.data("validation"+b+"Match")+'"]').first();return c.bind("validation.validation",function(){a.trigger("change.validation",{submitting:!0})}),{element:c}},validate:function(a,b,c){return b!==c.element.val()&&!c.negative||b===c.element.val()&&c.negative},blockSubmit:!0},max:{name:"max",init:function(a,b){return{max:a.data("validation"+b+"Max")}},validate:function(a,b,c){return parseFloat(b,10)>parseFloat(c.max,10)&&!c.negative||parseFloat(b,10)<=parseFloat(c.max,10)&&c.negative}},min:{name:"min",init:function(a,b){return{min:a.data("validation"+b+"Min")}},validate:function(a,b,c){return parseFloat(b)=parseFloat(c.min)&&c.negative}},maxlength:{name:"maxlength",init:function(a,b){return{maxlength:a.data("validation"+b+"Maxlength")}},validate:function(a,b,c){return b.length>c.maxlength&&!c.negative||b.length<=c.maxlength&&c.negative}},minlength:{name:"minlength",init:function(a,b){return{minlength:a.data("validation"+b+"Minlength")}},validate:function(a,b,c){return b.length=c.minlength&&c.negative}},maxchecked:{name:"maxchecked",init:function(a,b){var c=a.parents("form").first().find('[name="'+a.attr("name")+'"]');return c.bind("click.validation",function(){a.trigger("change.validation",{includeEmpty:!0})}),{maxchecked:a.data("validation"+b+"Maxchecked"),elements:c}},validate:function(a,b,c){return c.elements.filter(":checked").length>c.maxchecked&&!c.negative||c.elements.filter(":checked").length<=c.maxchecked&&c.negative},blockSubmit:!0},minchecked:{name:"minchecked",init:function(a,b){var c=a.parents("form").first().find('[name="'+a.attr("name")+'"]');return c.bind("click.validation",function(){a.trigger("change.validation",{includeEmpty:!0})}),{minchecked:a.data("validation"+b+"Minchecked"),elements:c}},validate:function(a,b,c){return c.elements.filter(":checked").length=c.minchecked&&c.negative},blockSubmit:!0}},builtInValidators:{email:{name:"Email",type:"shortcut",shortcut:"validemail"},validemail:{name:"Validemail",type:"regex",regex:"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}",message:"Not a valid email address"},passwordagain:{name:"Passwordagain",type:"match",match:"password",message:"Does not match the given password"},positive:{name:"Positive",type:"shortcut",shortcut:"number,positivenumber"},negative:{name:"Negative",type:"shortcut",shortcut:"number,negativenumber"},number:{name:"Number",type:"regex",regex:"([+-]?\\d+(\\.\\d*)?([eE][+-]?[0-9]+)?)?",message:"Must be a number"},integer:{name:"Integer",type:"regex",regex:"[+-]?\\d+",message:"No decimal places allowed"},positivenumber:{name:"Positivenumber",type:"min",min:0,message:"Must be a positive number"},negativenumber:{name:"Negativenumber",type:"max",max:0,message:"Must be a negative number"},required:{name:"Required",type:"required",message:"This is required"},checkone:{name:"Checkone",type:"minchecked",minchecked:1,message:"Check at least one option"}}},f=function(a){return a.toLowerCase().replace(/(^|\s)([a-z])/g,function(a,b,c){return b+c.toUpperCase()})},g=function(b){var c=b.val(),d=b.attr("type");return"checkbox"===d&&(c=b.is(":checked")?c:""),"radio"===d&&(c=a('input[name="'+b.attr("name")+'"]:checked').length>0?c:""),c};a.fn.jqBootstrapValidation=function(b){return e.methods[b]?e.methods[b].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof b&&b?(a.error("Method "+b+" does not exist on jQuery.jqBootstrapValidation"),null):e.methods.init.apply(this,arguments)},a.jqBootstrapValidation=function(){a(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this,arguments)}}(jQuery),$(function(){$("body").on("input propertychange",".floating-label-form-group",function(a){$(this).toggleClass("floating-label-form-group-with-value",!!$(a.target).val())}).on("focus",".floating-label-form-group",function(){$(this).addClass("floating-label-form-group-with-focus")}).on("blur",".floating-label-form-group",function(){$(this).removeClass("floating-label-form-group-with-focus")})}),jQuery(document).ready(function(a){var b=1170;if(a(window).width()>b){var c=a(".navbar-custom").height();a(window).on("scroll",{previousTop:0},function(){var b=a(window).scrollTop();b0&&a(".navbar-custom").hasClass("is-fixed")?a(".navbar-custom").addClass("is-visible"):a(".navbar-custom").removeClass("is-visible is-fixed"):(a(".navbar-custom").removeClass("is-visible"),b>c&&!a(".navbar-custom").hasClass("is-fixed")&&a(".navbar-custom").addClass("is-fixed")),this.previousTop=b})}}); -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | // routes.js 2 | import React from 'react' 3 | import { Route, IndexRoute } from 'react-router' 4 | 5 | // Store 6 | import AppStore from './stores/AppStore' 7 | 8 | // Main component 9 | import App from './components/App' 10 | 11 | // Pages 12 | import Blog from './components/Pages/Blog' 13 | import Default from './components/Pages/Default' 14 | import Work from './components/Pages/Work' 15 | import NoMatch from './components/Pages/NoMatch' 16 | 17 | export default ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | -------------------------------------------------------------------------------- /stores/AppStore.js: -------------------------------------------------------------------------------- 1 | // AppStore.js 2 | import { EventEmitter } from 'events' 3 | import _ from 'lodash' 4 | 5 | export default _.extend({}, EventEmitter.prototype, { 6 | 7 | // Initial data 8 | data: { 9 | ready: false, 10 | globals: {}, 11 | pages: [], 12 | item_num: 5 13 | }, 14 | 15 | // Emit Change event 16 | emitChange: function(){ 17 | this.emit('change') 18 | }, 19 | 20 | // Add change listener 21 | addChangeListener: function(callback){ 22 | this.on('change', callback) 23 | }, 24 | 25 | // Remove change listener 26 | removeChangeListener: function(callback) { 27 | this.removeListener('change', callback) 28 | } 29 | 30 | }) -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{ site.title }}{{# page }} | {{ page.title }}{{/ page }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 |
    {{{ reactMarkup }}}
    28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // webpack.config.js 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | devtool: 'eval', 6 | entry: './app-client.js', 7 | output: { 8 | path: __dirname + '/public/dist', 9 | filename: 'bundle.js', 10 | publicPath: '/dist/' 11 | }, 12 | module: { 13 | loaders: [ 14 | { test: /\.js$/, loaders: 'babel-loader', exclude: /node_modules/ }, 15 | { test: /\.jsx$/, loaders: 'babel-loader', exclude: /node_modules/ } 16 | ] 17 | }, 18 | plugins: [ 19 | new webpack.DefinePlugin({ 20 | 'process.env.COSMIC_BUCKET': JSON.stringify(process.env.COSMIC_BUCKET), 21 | 'process.env.COSMIC_READ_KEY': JSON.stringify(process.env.COSMIC_READ_KEY), 22 | 'process.env.COSMIC_WRITE_KEY': JSON.stringify(process.env.COSMIC_WRITE_KEY) 23 | }) 24 | ] 25 | }; 26 | --------------------------------------------------------------------------------