├── .eslintrc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build └── babelRelayPlugin.js ├── package.json ├── publish.sh ├── scripts └── updateSchema.js ├── src ├── client │ ├── Post.js │ ├── PostList.js │ ├── _post │ │ ├── bloqlPost.js │ │ ├── generateRootComponent.js │ │ └── postStore.js │ ├── _postlist │ │ ├── bloqlPostList.js │ │ ├── generateBlogComponent.js │ │ ├── generateRootComponent.js │ │ └── postListStore.js │ └── routes │ │ ├── BlogRoute.js │ │ └── PostRoute.js └── server │ ├── config.js │ ├── middleware │ └── express.js │ └── schema │ ├── classes.js │ ├── metaType.js │ ├── schema.js │ └── types.js └── webpack.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | parser: babel-eslint 4 | 5 | plugins: 6 | - react 7 | 8 | env: 9 | node: true 10 | es6: true 11 | 12 | globals: 13 | document: false 14 | 15 | arrowFunctions: true 16 | blockBindings: true 17 | classes: true 18 | defaultParams: true 19 | destructuring: true 20 | forOf: true 21 | generators: true 22 | modules: true 23 | objectLiteralComputedProperties: true 24 | objectLiteralShorthandMethods: true 25 | objectLiteralShorthandProperties: true 26 | spread: true 27 | templateStrings: true 28 | 29 | rules: 30 | # ERRORS 31 | brace-style: [2, 1tbs, allowSingleLine: true] 32 | camelcase: [2, properties: always] 33 | comma-style: [2, last] 34 | curly: [2, all] 35 | eol-last: 2 36 | eqeqeq: 2 37 | guard-for-in: 2 38 | handle-callback-err: [2, error] 39 | indent: [2, 2, SwitchCase: 1] 40 | key-spacing: [2, {beforeColon: false, afterColon: true}] 41 | max-len: [2, 160, 4, ignorePattern: "^(\\s*var\\s.+=\\s*require\\s*\\(|import )"] 42 | new-parens: 2 43 | no-alert: 2 44 | no-array-constructor: 2 45 | no-caller: 2 46 | no-catch-shadow: 2 47 | no-cond-assign: 2 48 | no-constant-condition: 2 49 | no-delete-var: 2 50 | no-div-regex: 2 51 | no-dupe-args: 2 52 | no-dupe-keys: 2 53 | no-duplicate-case: 2 54 | no-empty-character-class: 2 55 | no-empty-label: 2 56 | no-empty: 2 57 | no-eval: 2 58 | no-ex-assign: 2 59 | no-extend-native: 2 60 | no-extra-bind: 2 61 | no-extra-boolean-cast: 2 62 | no-extra-semi: 2 63 | no-fallthrough: 2 64 | no-floating-decimal: 2 65 | no-func-assign: 2 66 | no-implied-eval: 2 67 | no-inner-declarations: [2, functions] 68 | no-invalid-regexp: 2 69 | no-irregular-whitespace: 2 70 | no-iterator: 2 71 | no-label-var: 2 72 | no-lonely-if: 2 73 | no-mixed-requires: [2, true] 74 | no-mixed-spaces-and-tabs: 2 75 | no-multi-spaces: 2 76 | no-multi-str: 2 77 | no-negated-in-lhs: 2 78 | no-new-object: 2 79 | no-new-require: 2 80 | no-new-wrappers: 2 81 | no-new: 2 82 | no-obj-calls: 2 83 | no-octal-escape: 2 84 | no-octal: 2 85 | no-param-reassign: 2 86 | no-path-concat: 2 87 | no-proto: 2 88 | no-redeclare: 2 89 | no-regex-spaces: 2 90 | no-return-assign: 2 91 | no-script-url: 2 92 | no-sequences: 2 93 | no-shadow-restricted-names: 2 94 | no-shadow: 2 95 | no-spaced-func: 2 96 | no-sparse-arrays: 2 97 | no-throw-literal: 2 98 | no-trailing-spaces: 2 99 | no-undef-init: 2 100 | no-undef: 2 101 | no-unreachable: 2 102 | no-unused-expressions: 2 103 | no-unused-vars: [2, {vars: all, args: after-used}] 104 | no-void: 2 105 | no-with: 2 106 | one-var: [2, never] 107 | operator-assignment: [2, always] 108 | quote-props: [2, as-needed] 109 | quotes: [2, single] 110 | radix: 2 111 | semi-spacing: [2, {before: false, after: true}] 112 | semi: [2, always] 113 | space-after-keywords: [2, always] 114 | space-before-blocks: [2, always] 115 | space-before-function-paren: [2, {anonymous: always, named: never}] 116 | space-infix-ops: [2, int32Hint: false] 117 | space-return-throw-case: 2 118 | space-unary-ops: [2, {words: true, nonwords: false}] 119 | spaced-comment: [2, always] 120 | use-isnan: 2 121 | valid-typeof: 2 122 | wrap-iife: 2 123 | yoda: [2, never, exceptRange: true] 124 | react/jsx-uses-react: 1 125 | 126 | # WARNINGS 127 | 128 | # DISABLED 129 | block-scoped-var: 0 130 | comma-dangle: 0 131 | complexity: 0 132 | consistent-return: 0 133 | consistent-this: 0 134 | default-case: 0 135 | dot-notation: 0 136 | func-names: 0 137 | func-style: 0 138 | max-nested-callbacks: 0 139 | new-cap: 0 140 | newline-after-var: 0 141 | no-console: 0 142 | no-control-regex: 0 143 | no-debugger: 0 144 | no-eq-null: 0 145 | no-inline-comments: 0 146 | no-labels: 0 147 | no-lone-blocks: 0 148 | no-loop-func: 0 149 | no-multiple-empty-lines: 0 150 | no-native-reassign: 0 151 | no-nested-ternary: 0 152 | no-new-func: 0 153 | no-process-env: 0 154 | no-process-exit: 0 155 | no-reserved-keys: 0 156 | no-restricted-modules: 0 157 | no-self-compare: 0 158 | no-ternary: 0 159 | no-undefined: 0 160 | no-underscore-dangle: 0 161 | no-use-before-define: 0 162 | no-var: 0 163 | no-warning-comments: 0 164 | padded-blocks: 0 165 | sort-vars: 0 166 | space-in-brackets: 0 167 | space-in-parens: 0 168 | strict: 0 169 | valid-jsdoc: 0 170 | vars-on-top: 0 171 | wrap-regex: 0 172 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [0.11.0](https://github.com/adriantoine/bloql/tree/0.10.0) (2015-10-12) 3 | 4 | - Changed bloql interface to avoid polluting `this.props` object and make it easier to access for posts: 5 | 6 | for `PostList`: 7 | ```js 8 | {this.props.posts.edges.map(edge => 9 | {edge.node.meta.title} 10 | )} 11 | // becomes: 12 | {this.props.bloql.posts.map(post => 13 | {post.meta.title} 14 | )} 15 | ``` 16 | 17 | for `Post` (to avoid polluting `this.props`, in case a user wants to have pass his own `post` prop, we shouldn't override it): 18 | ```js 19 | this.props.post 20 | // becomes: 21 | this.props.bloql.post 22 | ``` 23 | 24 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.11.0...0.10.1) 25 | 26 | ## [0.10.0](https://github.com/adriantoine/bloql/tree/0.10.0) (2015-10-07) 27 | 28 | - Added ability to dynamically update filters on the post list using bloql functions in an `PostList` component: 29 | ```js 30 | // Filter a list of posts by tags or date 31 | this.props.bloql.setFilters({ tags: ['trip', 'usa'], startDate: '2015-01-01' }); 32 | 33 | // Reset filters 34 | this.props.bloql.resetFilters(); 35 | ``` 36 | Example: https://github.com/adriantoine/bloql-examples/blob/master/filters/public/index.js#L10 37 | 38 | - Changed function names for better consistency (hopefully it will be the last time I do that): 39 | ```js 40 | import { setComponent } from 'bloql/Post'; 41 | import { setComponent } from 'bloql/PostList'; 42 | 43 | // become => 44 | 45 | import { createComponent } from 'bloql/Post'; 46 | import { createComponent } from 'bloql/PostList'; 47 | ``` 48 | 49 | - Just like `Post`, `PostList` becomes a big customisable object, see previous release. 50 | - This is not part of this repo/package but examples have been updated to demonstrate more features: [bloql-examples](https://github.com/adriantoine/bloql-examples) 51 | 52 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.9.1...0.10.0) 53 | 54 | ## [0.9.0](https://github.com/adriantoine/bloql/tree/0.9.0) (2015-10-06) 55 | - Added ability to update the slug of a post which will change the displayed post, just run this command in any bloql `Post` component: 56 | ```js 57 | this.props.bloql.setSlug('my-post-slug'); 58 | ``` 59 | Example: https://github.com/adriantoine/bloql-examples/blob/master/basic/public/post.js#L11 60 | 61 | - Changed interface and function names: 62 | ```js 63 | import createPost from 'bloql/Post'; 64 | import createPostList from 'bloql/PostList'; 65 | 66 | // become => 67 | 68 | import { setComponent } from 'bloql/Post'; 69 | import { setComponent } from 'bloql/PostList'; 70 | ``` 71 | - `Post` element is much easier to customise with your own routes or fragments, so if you require the `Post` component this way: 72 | 73 | ```js 74 | import Post from 'bloql/Post'; 75 | ``` 76 | you'll be able to change almost anything from this post before calling its method `setComponent` with your component. More documentation and examples to be coming, for now you can have a look at: https://github.com/adriantoine/bloql/blob/master/src/client/Post.js 77 | This cool stuff is not yet available for `PostList` but that should be in the next release 78 | 79 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.8.3...0.9.0) 80 | 81 | ## [0.8.3](https://github.com/adriantoine/bloql/tree/0.8.3) (2015-10-06) 82 | Small code fix and added back webpack options 83 | 84 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.8.2...0.8.3) 85 | 86 | ## [0.8.2](https://github.com/adriantoine/bloql/tree/0.8.2) (2015-10-05) 87 | - Fixed build process to use webpack 88 | - Fixed many issues with dependencies and multiple copies of React 89 | - Doesn't bundle `react` and `react-relay` packages to make files minimal and avoid build issues, they are now [peer dependencies](https://github.com/adriantoine/bloql/blob/master/package.json#L39-L40) 90 | - Changed folder structure and way to get files: 91 | - On the server side: 92 | ```js 93 | import bloql from 'bloql/server/middleware/express'; 94 | 95 | // become => 96 | 97 | import bloql from 'bloql/middleware/express'; 98 | ``` 99 | - On the client side: 100 | ```js 101 | import { createPost } from 'bloql/client'; 102 | import { createPostList } from 'bloql/client'; 103 | 104 | // become => 105 | 106 | import createPost from 'bloql/Post'; 107 | import createPostList from 'bloql/PostList'; 108 | ``` 109 | - Created [examples](https://github.com/adriantoine/bloql-examples) and updated [README.md](https://github.com/adriantoine/bloql/blob/master/README.md) 110 | 111 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.7.0...0.8.2) 112 | 113 | ## [0.7.0](https://github.com/adriantoine/bloql/tree/0.7.0) (2015-09-16) 114 | - Now the database that retrieves and process posts is in another module to make it more modular, in case people want to store and retrieve posts in another way (MongoDB, PostgreSQL, using an API, etc...) they would be able to write the own database module for bloql. Ex: https://github.com/adriantoine/adriantoine.com/blob/580c04261e9fc8f5000bcb8ead715e6b349f5ae7/server.js#L15 115 | 116 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.6.0...0.7.0) 117 | 118 | ## [0.6.0](https://github.com/adriantoine/bloql/tree/0.6.0) (2015-09-16) 119 | - Now able to filter a date range using `startDate` and `endDate` filters. Ex: https://github.com/adriantoine/adriantoine.com/blob/5cb31174cceea25183efbe27302e721901e9d74b/src/components/PostList.js#L9-L10 120 | 121 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.5.0...0.6.0) 122 | 123 | ## [0.5.0](https://github.com/adriantoine/bloql/tree/0.5.0) (2015-09-12) 124 | - Removed the `createPostItem` from the client interface so that now you can actually use any React elements as PostItem, you don't have to pass the `PostItem` static variable anymore to `PostList`. A `PostList` is the only element needed by bloql to generate a list of posts. Ex: https://github.com/adriantoine/adriantoine.com/blob/7118ddcdc00f7fcb72b4e1a48363a460c5c9d03a/src/components/PostList.js 125 | 126 | - You can now use filters: https://github.com/adriantoine/adriantoine.com/blob/7118ddcdc00f7fcb72b4e1a48363a460c5c9d03a/src/components/PostList.js#L9-L10 127 | 128 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.4.0...0.5.0) 129 | 130 | ## [0.4.0](https://github.com/adriantoine/bloql/tree/0.4.0) (2015-09-12) 131 | Added a post component to be able to create a post page. 132 | 133 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.3.0...0.4.0) 134 | 135 | ## [0.3.0](https://github.com/adriantoine/bloql/tree/0.3.0) (2015-09-11) 136 | Simplified interface for an easier way to create components. Documentation still needs to be written. 137 | 138 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.2.0...0.3.0) 139 | 140 | ## [0.2.0](https://github.com/adriantoine/bloql/tree/0.2.0) (2015-09-11) 141 | The first version of my `bloql` blog engine powered by React using Relay and GraphQL to serve blog posts. 142 | 143 | This is a pre-release, the interface is probably going to be simplified in further released and docs need to be updated. 144 | 145 | [Full Changelog](https://github.com/adriantoine/bloql/compare/0.1.0...0.2.0) 146 | 147 | ## [0.1.0](https://github.com/adriantoine/bloql/tree/0.1.0) (2015-09-11) 148 | Initial release 149 | 150 | [Full Changelog](https://github.com/adriantoine/bloql/compare/show...0.1.0) 151 | 152 | ## [show](https://github.com/adriantoine/bloql/tree/show) (2015-09-11) 153 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Adrien Antoine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bloql 2 | 3 | [![Stable version](https://img.shields.io/npm/v/bloql.svg)](https://www.npmjs.com/package/bloql) 4 | [![Dependency Status](https://img.shields.io/gemnasium/adriantoine/bloql.svg)](https://gemnasium.com/adriantoine/bloql) 5 | 6 | Blog engine powered by [React](https://facebook.github.io/react/) using [Relay](https://facebook.github.io/relay/) and [GraphQL](https://facebook.github.io/graphql/) to interact with data. 7 | 8 | # Usage 9 | - Install `bloql` package and a bloql retriever to get files: 10 | ```bash 11 | npm install bloql bloql-markdown-file-database --save 12 | ``` 13 | 14 | - Create a backend to serve blog posts: 15 | ```js 16 | var path = require('path'); 17 | var express = require('express'); 18 | var bloql = require('bloql/middleware/express'); 19 | 20 | const app = express(); 21 | 22 | bloql(app, { 23 | pretty: true, 24 | postsPath: path.join(__dirname, 'posts'), 25 | database: require('bloql-markdown-file-database') 26 | }); 27 | 28 | ... 29 | 30 | app.listen(3000, () => { 31 | console.log('Server started and listening on port 3000'); 32 | }); 33 | ``` 34 | (for now only available for `express`) 35 | 36 | - Now you're all set to use bloql on the client: 37 | ```js 38 | import React, { Component } from 'react'; 39 | import ReactDOM from 'react-dom'; 40 | import { createComponent } from 'bloql/PostList'; 41 | 42 | class PostList extends Component { 43 | 44 | render() { 45 | 46 | // Render your post list using all react components you want 47 | return ( 48 | 53 | ); 54 | 55 | } 56 | 57 | } 58 | 59 | // Convert your component into a Bloql element 60 | PostList = createComponent(PostList); 61 | 62 | // You can place your component anywhere in any application and 63 | // combine it with other React components 64 | ReactDOM.render( 65 | , 66 | document.getElementById('app') 67 | ); 68 | ``` 69 | 70 | Have a look there for minimal and understandable examples: [bloql-examples](https://github.com/adriantoine/bloql-examples) 71 | -------------------------------------------------------------------------------- /build/babelRelayPlugin.js: -------------------------------------------------------------------------------- 1 | var getBabelRelayPlugin = require('babel-relay-plugin'); 2 | var schema = require('../dist/schema/schema.json'); 3 | 4 | module.exports = getBabelRelayPlugin(schema.data); 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bloql", 3 | "version": "0.11.0", 4 | "description": "Blog engine powered by React using Relay and GraphQL to interact with data", 5 | "scripts": { 6 | "dev": "webpack --watch --progress", 7 | "clean": "rm -rf dist/", 8 | "compile-server": "babel src/server --out-dir dist", 9 | "cp-files": "cp package.json dist/ && cp README.md dist/", 10 | "lint": "eslint src", 11 | "update-schema": "babel-node ./scripts/updateSchema.js", 12 | "watch": "gulp watch", 13 | "build": "npm run clean && npm run lint && npm run compile-server && npm run update-schema && webpack && npm run cp-files" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+ssh://git@github.com:adriantoine/bloql.git" 18 | }, 19 | "keywords": [ 20 | "blog", 21 | "engine", 22 | "react", 23 | "relay", 24 | "graphql" 25 | ], 26 | "author": "Adrien Antoine (http://adriantoine.com/)", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/adriantoine/bloql/issues", 30 | "email": "adriantoine@gmail.com" 31 | }, 32 | "homepage": "https://github.com/adriantoine/bloql#readme", 33 | "dependencies": { 34 | "express-graphql": "^0.4.0", 35 | "graphql": "^0.4.7", 36 | "graphql-relay": "^0.3.2", 37 | "lodash": "^3.10.1" 38 | }, 39 | "peerDependencies": { 40 | "react": "^0.14.0", 41 | "react-relay": "^0.3.2" 42 | }, 43 | "devDependencies": { 44 | "babel": "^5.8.23", 45 | "babel-eslint": "^4.1.1", 46 | "babel-loader": "^5.3.2", 47 | "babel-relay-plugin": "^0.2.6", 48 | "eslint": "^1.3.1", 49 | "eslint-loader": "^1.0.0", 50 | "eslint-plugin-react": "^3.3.1", 51 | "webpack": "^1.12.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | rm -rf node_modules 4 | 5 | npm install && 6 | # npm test && 7 | npm version ${1:-patch} && 8 | npm run build 9 | 10 | cd dist/ 11 | npm publish 12 | 13 | git push origin master --follow-tags 14 | -------------------------------------------------------------------------------- /scripts/updateSchema.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env babel-node --optional es7.asyncFunctions 2 | 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import Schema from '../src/server/schema/schema'; 6 | import { graphql } from 'graphql'; 7 | import { introspectionQuery, printSchema } from 'graphql/utilities'; 8 | 9 | // Save JSON of full schema introspection for Babel Relay Plugin to use 10 | async () => { 11 | var result = await (graphql(Schema, introspectionQuery)); 12 | if (result.errors) { 13 | console.error( 14 | 'ERROR introspecting schema: ', 15 | JSON.stringify(result.errors, null, 2) 16 | ); 17 | } else { 18 | fs.writeFileSync( 19 | path.join(__dirname, '../dist/schema/schema.json'), 20 | JSON.stringify(result, null, 2) 21 | ); 22 | } 23 | }(); 24 | 25 | // Save user readable type system shorthand of schema 26 | fs.writeFileSync( 27 | path.join(__dirname, '../dist/schema/schema.graphql'), 28 | printSchema(Schema) 29 | ); 30 | -------------------------------------------------------------------------------- /src/client/Post.js: -------------------------------------------------------------------------------- 1 | 2 | import Relay from 'react-relay'; 3 | import React from 'react'; 4 | 5 | import DefaultRoute from './routes/PostRoute'; 6 | import generateRootComponent from './_post/generateRootComponent'; 7 | import bloqlPost from './_post/bloqlPost'; 8 | 9 | class Post { 10 | 11 | constructor() { 12 | 13 | // Create initial fragment 14 | this.fragment = Relay.QL` 15 | fragment on Post { 16 | meta { 17 | title 18 | slug 19 | date 20 | categories 21 | tags 22 | }, 23 | content 24 | } 25 | `; 26 | 27 | // Set default route 28 | this.route = DefaultRoute; 29 | 30 | // Set the functions there to allow it to be overriden 31 | this.generateRootComponent = generateRootComponent; 32 | 33 | // Create a very generic component to begin 34 | this.createComponent(React.createClass({ 35 | render: function () { return
; } 36 | })); 37 | 38 | } 39 | 40 | // Generate components from the React one provided 41 | createComponent(component) { 42 | 43 | this.Component = component; 44 | 45 | this.Bloql = this.createBloql(this.Component); 46 | this.Relay = this.createRelay(this.Bloql); 47 | this.Root = this.createRoot(this.Relay); 48 | 49 | return this.Root; 50 | 51 | } 52 | 53 | // Generate bloql post element with custom functions 54 | createBloql(component) { 55 | return bloqlPost(component); 56 | } 57 | 58 | // Generate Relay component 59 | createRelay(component) { 60 | return Relay.createContainer(component, { 61 | fragments: { 62 | post: () => this.fragment, 63 | } 64 | }); 65 | } 66 | 67 | // Generate Relay Root Container 68 | createRoot(component) { 69 | return this.generateRootComponent(component, this.Component.slug, this.route); 70 | } 71 | 72 | } 73 | 74 | var post = new Post(); 75 | 76 | export var createComponent = post.createComponent.bind(post); 77 | export default post; 78 | -------------------------------------------------------------------------------- /src/client/PostList.js: -------------------------------------------------------------------------------- 1 | 2 | import Relay from 'react-relay'; 3 | import DefaultRoute from './routes/BlogRoute'; 4 | import { generateBlogReactComponent, generateBlogRelayComponent } from './_postlist/generateBlogComponent'; 5 | import generateRootComponent from './_postlist/generateRootComponent'; 6 | import bloqlPostList from './_postlist/bloqlPostList'; 7 | 8 | class PostList { 9 | 10 | constructor() { 11 | 12 | // Create post list initial fragment 13 | this.postListFragment = Relay.QL` 14 | fragment on PostConnection { 15 | edges { 16 | node { 17 | id, 18 | meta { 19 | title 20 | slug 21 | date 22 | categories 23 | tags 24 | } 25 | } 26 | } 27 | } 28 | `; 29 | 30 | // Set default route 31 | this.route = DefaultRoute; 32 | 33 | // Set the functions there to allow it to be overriden 34 | this.generateBlogReactComponent = generateBlogReactComponent; 35 | this.generateBlogRelayComponent = generateBlogRelayComponent; 36 | this.generateRootComponent = generateRootComponent; 37 | 38 | } 39 | 40 | // Set all components 41 | createComponent(component) { 42 | 43 | this.Component = component; 44 | 45 | this.Bloql = this.createBloql(this.Component); 46 | this.Relay = this.createRelay(this.Bloql); 47 | this.Blog = this.createBlog(this.Relay, this.Component); 48 | this.Root = this.createRoot(this.Blog); 49 | 50 | return this.Root; 51 | 52 | } 53 | 54 | // Generate bloql post element with custom functions 55 | createBloql(component) { 56 | return bloqlPostList(component); 57 | } 58 | 59 | createRelay(component) { 60 | return Relay.createContainer(component, { 61 | fragments: { 62 | posts: () => this.postListFragment, 63 | }, 64 | }); 65 | } 66 | 67 | createBlog(component) { 68 | var ReactComponent = this.generateBlogReactComponent(component); 69 | var RelayComponent = this.generateBlogRelayComponent(ReactComponent, this.Component.filters || {}, component); 70 | 71 | return RelayComponent; 72 | } 73 | 74 | createRoot(component) { 75 | return generateRootComponent(component, this.route); 76 | } 77 | 78 | } 79 | 80 | var postList = new PostList(); 81 | 82 | export var createComponent = postList.createComponent.bind(postList); 83 | export default postList; 84 | -------------------------------------------------------------------------------- /src/client/_post/bloqlPost.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import _ from 'lodash'; 4 | 5 | import postStore from './postStore'; 6 | 7 | export default function (Component) { 8 | 9 | return React.createClass({ 10 | 11 | setSlug: function (slug) { 12 | postStore.setSlug(slug); 13 | }, 14 | 15 | render: function () { 16 | 17 | var props = _.extend({ 18 | bloql: { 19 | setSlug: this.setSlug, 20 | post: this.props.post, 21 | } 22 | }, this.props, postStore.props); 23 | 24 | return ; 25 | } 26 | 27 | }); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/client/_post/generateRootComponent.js: -------------------------------------------------------------------------------- 1 | 2 | import postStore from './postStore'; 3 | import { RootContainer } from 'react-relay'; 4 | import React, { Component } from 'react'; 5 | 6 | // This function will generate a root component based on parameters 7 | export default function (relay, staticSlug, PostRoute) { 8 | return class RootComponent extends Component { 9 | 10 | constructor() { 11 | super(); 12 | 13 | // Make the function setSlug available in the store for other components 14 | postStore.setSlug = this.setSlug.bind(this); 15 | 16 | } 17 | 18 | // Update current slug 19 | setSlug(slug) { 20 | this.setState({ 21 | slug: slug 22 | }); 23 | } 24 | 25 | getSlug() { 26 | 27 | // Get a default slug 28 | var slug = this.props.slug || staticSlug; 29 | 30 | // Get slug from state if it's set 31 | if (this.state && this.state.slug) { 32 | slug = this.state.slug; 33 | } 34 | 35 | return slug; 36 | 37 | } 38 | 39 | render() { 40 | 41 | // Pass props to react component 42 | postStore.props = this.props; 43 | 44 | return ; 45 | 46 | } 47 | 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/client/_post/postStore.js: -------------------------------------------------------------------------------- 1 | 2 | // Store used to link component and root together 3 | export default { 4 | setSlug: function () {} 5 | }; 6 | -------------------------------------------------------------------------------- /src/client/_postlist/bloqlPostList.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import _ from 'lodash'; 4 | 5 | import postListStore from './postListStore'; 6 | 7 | const processPostList = (edges) => { 8 | return _.map(edges, (edge) => edge.node); 9 | }; 10 | 11 | export default function (Component) { 12 | 13 | return React.createClass({ 14 | 15 | setFilters: function (filters) { 16 | postListStore.setFilters(filters); 17 | }, 18 | resetFilters: function () { 19 | postListStore.resetFilters(); 20 | }, 21 | 22 | render: function () { 23 | 24 | var props = _.extend({ 25 | bloql: { 26 | setFilters: this.setFilters, 27 | resetFilters: this.resetFilters, 28 | posts: processPostList(this.props.posts.edges), 29 | } 30 | }, this.props, postListStore.props); 31 | 32 | return ; 33 | } 34 | 35 | }); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/client/_postlist/generateBlogComponent.js: -------------------------------------------------------------------------------- 1 | 2 | import Relay from 'react-relay'; 3 | import _ from 'lodash'; 4 | import React, { Component } from 'react'; 5 | 6 | import postListStore from './postListStore'; 7 | 8 | var originalFilters = { 9 | count: 10, 10 | startDate: null, 11 | endDate: null, 12 | date: null, 13 | categories: null, 14 | tags: null, 15 | }; 16 | 17 | // This function will generate a root component based on parameters 18 | export const generateBlogReactComponent = function (RelayPostList) { 19 | return class RootComponent extends Component { 20 | 21 | constructor() { 22 | super(); 23 | 24 | // Make the functions available in the store for other components 25 | postListStore.setFilters = this.setFilters.bind(this); 26 | postListStore.resetFilters = this.resetFilters.bind(this); 27 | 28 | } 29 | 30 | // Update current filters 31 | setFilters(filters) { 32 | this.props.relay.setVariables(filters); 33 | } 34 | 35 | // Reset filters 36 | resetFilters() { 37 | this.props.relay.setVariables(originalFilters); 38 | } 39 | 40 | render() { 41 | return ; 42 | } 43 | }; 44 | }; 45 | 46 | export const generateBlogRelayComponent = function (ReactComponent, componentStaticFilters, RelayPostList) { 47 | 48 | // Store original filters in an array 49 | _.extend(originalFilters, componentStaticFilters); 50 | 51 | return Relay.createContainer(ReactComponent, { 52 | 53 | initialVariables: originalFilters, 54 | 55 | fragments: { 56 | blog: () => { 57 | return Relay.QL` 58 | fragment on Blog { 59 | posts(first: $count startDate:$startDate endDate:$endDate date:$date categories:$categories tags:$tags) { 60 | ${RelayPostList.getFragment('posts')} 61 | } 62 | } 63 | `; 64 | }, 65 | }, 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /src/client/_postlist/generateRootComponent.js: -------------------------------------------------------------------------------- 1 | 2 | import { RootContainer } from 'react-relay'; 3 | import React, { Component } from 'react'; 4 | 5 | import postListStore from './postListStore'; 6 | 7 | // This function will generate a root component based on parameters 8 | export default function (component, PostRoute) { 9 | return class Root extends Component { 10 | render() { 11 | 12 | // Pass props to react component 13 | postListStore.props = this.props; 14 | 15 | return ( 16 | 17 | ); 18 | } 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/client/_postlist/postListStore.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // Store used to link component and root together 4 | export default { 5 | setFilters: function () {}, 6 | resetFilters: function () {}, 7 | }; 8 | -------------------------------------------------------------------------------- /src/client/routes/BlogRoute.js: -------------------------------------------------------------------------------- 1 | 2 | import Relay from 'react-relay'; 3 | 4 | export default class extends Relay.Route { 5 | 6 | static queries = { 7 | blog: () => Relay.QL`query { blog }` 8 | }; 9 | 10 | static routeName = 'BlogRoute'; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/client/routes/PostRoute.js: -------------------------------------------------------------------------------- 1 | 2 | import Relay from 'react-relay'; 3 | 4 | export default class extends Relay.Route { 5 | 6 | static queries = { 7 | post: () => Relay.QL`query { post(slug: $slug) }`, 8 | }; 9 | 10 | static routeName = 'PostRoute'; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/server/config.js: -------------------------------------------------------------------------------- 1 | 2 | var database = null; 3 | 4 | module.exports.setConfig = function (options) { 5 | database = options.database || null; 6 | database.setConfig(options); 7 | }; 8 | 9 | module.exports.getDatabase = function () { 10 | return database; 11 | }; 12 | -------------------------------------------------------------------------------- /src/server/middleware/express.js: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'lodash'; 3 | import graphqlHTTP from 'express-graphql'; 4 | import { setConfig } from '../config'; 5 | 6 | 7 | export default function (app, options) { 8 | 9 | let graphQLOptions = _.extend({ 10 | schema: require('../schema/schema') 11 | }, options); 12 | 13 | setConfig(options); 14 | 15 | app.use('/graphql', graphqlHTTP(graphQLOptions)); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/server/schema/classes.js: -------------------------------------------------------------------------------- 1 | 2 | // Elements needed by Relay 3 | // TODO: Investigate how to get rid of these 4 | // ------------------------------ 5 | 6 | export class Blog extends Object {} 7 | export class Post extends Object {} 8 | 9 | var blog = new Blog(); 10 | blog.id = 1; 11 | 12 | export function getBlog() { 13 | return blog; 14 | } 15 | -------------------------------------------------------------------------------- /src/server/schema/metaType.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | GraphQLObjectType, 4 | GraphQLNonNull, 5 | GraphQLString, 6 | GraphQLBoolean, 7 | GraphQLList, 8 | } from 'graphql/type'; 9 | 10 | export default new GraphQLObjectType({ 11 | name: 'Meta', 12 | description: 'Blog post metadata', 13 | fields: () => ({ 14 | title: { 15 | type: new GraphQLNonNull(GraphQLString), 16 | description: 'Title of the blog post', 17 | }, 18 | slug: { 19 | type: new GraphQLNonNull(GraphQLString), 20 | description: 'Slug of the blog post', 21 | }, 22 | date: { 23 | type: new GraphQLNonNull(GraphQLString), 24 | description: 'Date of the blog post', 25 | }, 26 | comments: { 27 | type: GraphQLBoolean, 28 | description: 'Choose if we want to show comments or not', 29 | }, 30 | categories: { 31 | type: new GraphQLList(GraphQLString), 32 | description: 'Blog post categories', 33 | }, 34 | tags: { 35 | type: new GraphQLList(GraphQLString), 36 | description: 'Blog post tags', 37 | }, 38 | }) 39 | }); 40 | -------------------------------------------------------------------------------- /src/server/schema/schema.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | GraphQLObjectType, 4 | GraphQLSchema, 5 | GraphQLString, 6 | } from 'graphql/type'; 7 | 8 | import { getBlog } from './classes'; 9 | import { getDatabase } from '../config'; 10 | import { blogType, postType, node } from './types'; 11 | 12 | var Root = new GraphQLObjectType({ 13 | 14 | name: 'Root', 15 | 16 | fields: () => ({ 17 | 18 | node: node.nodeField, 19 | 20 | blog: { 21 | type: blogType, 22 | resolve: () => getBlog() 23 | }, 24 | 25 | post: { 26 | type: postType, 27 | args: { 28 | slug: { 29 | type: GraphQLString, 30 | }, 31 | }, 32 | resolve: (root, args) => getDatabase().getPostList(args)[0], 33 | } 34 | 35 | }) 36 | 37 | }); 38 | 39 | var schema = new GraphQLSchema({ 40 | query: Root 41 | }); 42 | 43 | export default schema; 44 | -------------------------------------------------------------------------------- /src/server/schema/types.js: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'lodash'; 3 | 4 | import { 5 | GraphQLObjectType, 6 | GraphQLNonNull, 7 | GraphQLString, 8 | GraphQLList, 9 | } from 'graphql/type'; 10 | 11 | import { 12 | connectionArgs, 13 | connectionFromArray, 14 | globalIdField, 15 | nodeDefinitions, 16 | fromGlobalId, 17 | connectionDefinitions, 18 | } from 'graphql-relay'; 19 | 20 | import { Post, Blog, getBlog } from './classes'; 21 | import { getDatabase } from '../config'; 22 | import metaType from './metaType'; 23 | 24 | export const node = nodeDefinitions( 25 | 26 | (globalId) => { 27 | 28 | var {type, id} = fromGlobalId(globalId); 29 | 30 | if (type === 'Post') { 31 | return getDatabase().getPostList({slug: id})[0]; 32 | } else if (type === 'Blog') { 33 | return getBlog(); 34 | } 35 | 36 | return null; 37 | 38 | }, 39 | 40 | (obj) => { 41 | 42 | if (obj instanceof Post) { 43 | return postType; 44 | } else if (obj instanceof Blog) { 45 | return blogType; 46 | } 47 | 48 | return null; 49 | 50 | } 51 | 52 | ); 53 | 54 | export const postType = new GraphQLObjectType({ 55 | 56 | name: 'Post', 57 | description: 'Blog post', 58 | 59 | fields: () => ({ 60 | id: globalIdField('Post'), 61 | meta: { 62 | type: metaType, 63 | description: 'Metadata of the blog post', 64 | }, 65 | content: { 66 | type: new GraphQLNonNull(GraphQLString), 67 | description: 'Blog post content in HTML', 68 | }, 69 | }), 70 | 71 | interfaces: [ node.nodeInterface ] 72 | 73 | }); 74 | 75 | export const connection = connectionDefinitions({ 76 | name: 'Post', 77 | nodeType: postType 78 | }); 79 | 80 | export const blogType = new GraphQLObjectType({ 81 | name: 'Blog', 82 | 83 | fields: { 84 | 85 | id: globalIdField('Blog'), 86 | 87 | posts: { 88 | type: connection.connectionType, 89 | description: 'Blog posts', 90 | 91 | args: _.extend(connectionArgs, { 92 | startDate: { 93 | type: GraphQLString 94 | }, 95 | endDate: { 96 | type: GraphQLString 97 | }, 98 | date: { 99 | type: GraphQLString 100 | }, 101 | categories: { 102 | type: new GraphQLList(GraphQLString) 103 | }, 104 | tags: { 105 | type: new GraphQLList(GraphQLString) 106 | } 107 | }), 108 | 109 | resolve: (blog, args) => connectionFromArray(getDatabase().getPostList({ 110 | startDate: args.startDate, 111 | endDate: args.endDate, 112 | date: args.date, 113 | categories: args.categories, 114 | tags: args.tags 115 | }), args), 116 | } 117 | 118 | }, 119 | 120 | interfaces: [ node.nodeInterface ] 121 | }); 122 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var webpack = require('webpack'); 4 | 5 | module.exports = { 6 | 7 | devtool: 'source-map', 8 | 9 | entry: { 10 | PostList: './src/client/PostList', 11 | Post: './src/client/Post' 12 | }, 13 | 14 | output: { 15 | filename: '[name].js', 16 | library: 'Bloql', 17 | libraryTarget: 'umd', 18 | path: path.join(__dirname, 'dist') 19 | }, 20 | 21 | module: { 22 | loaders: [{ 23 | test: /\.js$/, 24 | loader: 'babel-loader', 25 | query: {stage: 0, plugins: ['./build/babelRelayPlugin']}, 26 | exclude: /node_modules/ 27 | }] 28 | }, 29 | 30 | 31 | plugins: [ 32 | new webpack.optimize.DedupePlugin(), 33 | new webpack.optimize.OccurenceOrderPlugin(), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { warnings: false }, 36 | output: { comments: false } 37 | }) 38 | ], 39 | 40 | 41 | 42 | externals: { 43 | react: { 44 | root: 'React', 45 | commonjs2: 'react', 46 | commonjs: 'react', 47 | amd: 'react' 48 | }, 49 | 'react-relay': { 50 | root: 'Relay', 51 | commonjs2: 'react-relay', 52 | commonjs: 'react-relay', 53 | amd: 'react-relay' 54 | } 55 | } 56 | 57 | }; 58 | --------------------------------------------------------------------------------