├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── build └── babelRelayPlugin.js ├── data ├── data.js ├── database.js ├── schema.js └── schema.json ├── js ├── app.js ├── components │ ├── ChatApp.js │ ├── MessageComposer.js │ ├── MessageListItem.js │ ├── MessageSection.js │ ├── ThreadListItem.js │ └── ThreadSection.js ├── mutations │ ├── AddMessageMutation.js │ └── MarkThreadAsReadMutation.js └── routes │ ├── chatApp.js │ ├── index.js │ └── messageSection.js ├── package.json ├── public ├── app.css ├── index.html └── learn.json ├── scripts └── updateSchema.js ├── server.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "loose": "all" 4 | } 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/webpack.config.js 3 | server.js 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | parser: babel-eslint 3 | 4 | plugins: 5 | - react 6 | 7 | env: 8 | node: true 9 | es6: true 10 | 11 | globals: 12 | document: false 13 | window: 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, 80, 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-sync: 2 98 | no-throw-literal: 2 99 | no-trailing-spaces: 2 100 | no-undef-init: 2 101 | no-undef: 2 102 | no-unreachable: 2 103 | no-unused-expressions: 2 104 | no-unused-vars: [2, {vars: all, args: after-used}] 105 | no-void: 2 106 | no-with: 2 107 | one-var: [2, never] 108 | operator-assignment: [2, always] 109 | quote-props: [2, as-needed] 110 | quotes: [2, single] 111 | radix: 2 112 | semi-spacing: [2, {before: false, after: true}] 113 | semi: [2, always] 114 | space-after-keywords: [2, always] 115 | space-before-blocks: [2, always] 116 | space-before-function-paren: [2, {anonymous: always, named: never}] 117 | space-infix-ops: [2, int32Hint: false] 118 | space-return-throw-case: 2 119 | space-unary-ops: [2, {words: true, nonwords: false}] 120 | spaced-comment: [2, always] 121 | use-isnan: 2 122 | valid-typeof: 2 123 | wrap-iife: 2 124 | yoda: [2, never, exceptRange: true] 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 | .DS_Store 2 | node_modules 3 | *.log 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 CHEN HUNG TU 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Relay Chat 2 | 3 | **I no longer maintain this project. Since it is a demonstration, you can still fork and play with it** 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install --global babel 9 | npm install 10 | ``` 11 | 12 | ## Running 13 | 14 | Start a local server: 15 | 16 | ``` 17 | npm start 18 | ``` 19 | 20 | ## Developing 21 | 22 | Any changes you make to files in the `js/` directory will cause the server to 23 | automatically rebuild the app and refresh your browser. 24 | 25 | If at any time you make changes to `data/schema.js`, stop the server, 26 | regenerate `data/schema.json`, and restart the server: 27 | 28 | ``` 29 | npm run update-schema 30 | npm start 31 | ``` 32 | 33 | ## Feature Highlight: 34 | 35 | 1. use with [relay-nested-routes](https://github.com/devknoll/relay-nested-routes) and [react-router](https://github.com/rackt/react-router) to let multiple routes work and route params as query params 36 | 37 | 2. using route params as global state variable (ex: currentThreadID), not ideal for now, but it shows a different possibility 38 | 39 | 3. and a more complicated model structure as `user-> threads-> messages` 40 | 4. [pagination for messages](https://github.com/transedward/relay-chat/tree/add-pagination) 41 | 42 | 43 | ## Advice 44 | 45 | If you don't know much about GraphQL and Relay, I suggest you: 46 | 47 | 1. [To know GraphQL first](https://github.com/facebook/graphql/blob/master/README.md) 48 | (at least things before introspection part) 49 | 2. [Know how Relay connect to GraphQL](http://facebook.github.io/relay/docs/graphql-relay-specification.html#content) 50 | (and the series below) 51 | 3. [Last, learn Relay](http://facebook.github.io/relay/docs/guides-containers.html#content) 52 | (and the series below) 53 | 54 | I tried these 3 steps in reverse, and I went step by step again. 55 | -------------------------------------------------------------------------------- /build/babelRelayPlugin.js: -------------------------------------------------------------------------------- 1 | var getBabelRelayPlugin = require('babel-relay-plugin'); 2 | var schema = require('../data/schema.json'); 3 | 4 | module.exports = getBabelRelayPlugin(schema.data); 5 | -------------------------------------------------------------------------------- /data/data.js: -------------------------------------------------------------------------------- 1 | export class Thread extends Object {} 2 | export class Message extends Object {} 3 | export class User extends Object {} 4 | 5 | // Mock authenticated ID 6 | export const VIEWER_ID = 'me'; 7 | 8 | // Mock user data 9 | var viewer = new User(); 10 | viewer.id = VIEWER_ID; 11 | export var usersById = { 12 | [VIEWER_ID]: viewer 13 | }; 14 | 15 | export var threadsById = {}; 16 | export var threadIdsByUser = { 17 | [VIEWER_ID]: [] 18 | }; 19 | 20 | export var messagesById = {}; 21 | export var messageIdsByThread = {}; 22 | 23 | // Mock raw message data then we can transform 24 | var messages = [ 25 | { 26 | id: 'm_1', 27 | threadID: 't_1', 28 | threadName: 'Jing and Me', 29 | authorName: 'me', 30 | text: 'Hey Jing, want to give a Flux talk at ForwardJS?', 31 | timestamp: Date.now() - 99999 32 | }, 33 | { 34 | id: 'm_2', 35 | threadID: 't_1', 36 | threadName: 'Jing and me', 37 | authorName: 'me', 38 | text: 'Seems like a pretty cool conference.', 39 | timestamp: Date.now() - 89999 40 | }, 41 | { 42 | id: 'm_3', 43 | threadID: 't_1', 44 | threadName: 'Jing and me', 45 | authorName: 'Jing', 46 | text: 'Sounds good. Will they be serving dessert?', 47 | timestamp: Date.now() - 79999 48 | }, 49 | { 50 | id: 'm_4', 51 | threadID: 't_2', 52 | threadName: 'Dave and me', 53 | authorName: 'me', 54 | text: 'Hey Dave, want to get a beer after the conference?', 55 | timestamp: Date.now() - 69999 56 | }, 57 | { 58 | id: 'm_5', 59 | threadID: 't_2', 60 | threadName: 'Dave and me', 61 | authorName: 'Dave', 62 | text: 'Totally! Meet you at the hotel bar.', 63 | timestamp: Date.now() - 59999 64 | }, 65 | { 66 | id: 'm_6', 67 | threadID: 't_3', 68 | threadName: 'Brian and me', 69 | authorName: 'me', 70 | text: 'Hey Brian, are you going to be talking about functional stuff?', 71 | timestamp: Date.now() - 49999 72 | }, 73 | { 74 | id: 'm_7', 75 | threadID: 't_3', 76 | threadName: 'Brian and me', 77 | authorName: 'Brian', 78 | text: 'At ForwardJS? Yeah, of course. See you there!', 79 | timestamp: Date.now() - 39999 80 | } 81 | ]; 82 | // inject raw messages into database 83 | // 這裡我們將 flux-chat 轉成符合 user -> threads -> messages 這樣的結構 84 | // -> : has many 85 | messages.map(mes => { 86 | let {threadID, threadName, timestamp} = mes; 87 | // if thread not exists 88 | if (!threadsById[threadID]) { 89 | let thread = new Thread(); 90 | thread.id = threadID; 91 | thread.name = threadName; 92 | thread.isRead = false; 93 | thread.lastUpdated = timestamp; 94 | threadIdsByUser[VIEWER_ID].push(thread.id); 95 | threadsById[thread.id] = thread; 96 | } 97 | // if message are newer than lastUpdated, show update 98 | // 如果在同一個 thread 裡後面的訊息比較前面新, 更新 lastUpdated 99 | // 只有在這裡需要, 如果是透過 client 新增訊息, 就直接更新 lastUpdated 100 | if (timestamp > threadsById[threadID].lastUpdated) { 101 | threadsById[threadID].lastUpdated = timestamp; 102 | } 103 | let message = new Message(); 104 | let {id, authorName, text} = mes; 105 | message.id = id; 106 | message.authorName = authorName; 107 | message.text = text; 108 | message.timestamp = timestamp; 109 | messagesById[message.id] = message; 110 | // if threadID is not defined in messageIdsByThread 111 | if (!messageIdsByThread[threadID]) { 112 | messageIdsByThread[threadID] = []; 113 | } 114 | messageIdsByThread[threadID].push(message.id); 115 | }); 116 | -------------------------------------------------------------------------------- /data/database.js: -------------------------------------------------------------------------------- 1 | import {Message, VIEWER_ID, usersById, threadsById, threadIdsByUser, 2 | messagesById, messageIdsByThread} from './data'; 3 | 4 | export function addMessage(text, currentThreadID) { 5 | var timestamp = Date.now(); 6 | var message = new Message(); 7 | message.id = 'm_' + timestamp; 8 | message.authorName = 'me'; // hard coded for the example 9 | message.text = text; 10 | message.timestamp = timestamp; 11 | 12 | threadsById[currentThreadID].isRead = true; 13 | threadsById[currentThreadID].lastUpdated = timestamp; 14 | 15 | messagesById[message.id] = message; 16 | messageIdsByThread[currentThreadID].push(message.id); 17 | 18 | return { 19 | messageID: message.id, 20 | threadID: currentThreadID 21 | }; 22 | } 23 | 24 | export function markThreadAsRead(id) { 25 | var thread = getThread(id); 26 | thread.isRead = true; 27 | } 28 | 29 | export function getThread(id) { 30 | return threadsById[id]; 31 | } 32 | 33 | export function getThreads() { 34 | let orderedThreads = threadIdsByUser[VIEWER_ID].map(id => getThread(id)); 35 | // let newer thread get lower index 36 | orderedThreads.sort((x, y) => { 37 | return x.lastUpdated > y.lastUpdated ? 38 | -1 : x.lastUpdated < y.lastUpdated ? 1 : 0; 39 | }); 40 | return orderedThreads; 41 | } 42 | 43 | export function getMessage(id) { 44 | return messagesById[id]; 45 | } 46 | 47 | export function getMessagesByThreadId(threadID) { 48 | let orderedMessages = messageIdsByThread[threadID].map(id => getMessage(id)); 49 | // let newer message get higher index 50 | orderedMessages.sort((x, y) => { 51 | return x.timestamp < y.timestamp ? -1 : x.timestamp > y.timestamp ? 1 : 0; 52 | }); 53 | return orderedMessages; 54 | } 55 | 56 | export function getUser(id) { 57 | return usersById[id]; 58 | } 59 | 60 | export function getViewer() { 61 | return getUser(VIEWER_ID); 62 | } 63 | -------------------------------------------------------------------------------- /data/schema.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLBoolean, 3 | GraphQLID, 4 | GraphQLInt, 5 | GraphQLNonNull, 6 | GraphQLObjectType, 7 | GraphQLSchema, 8 | GraphQLString, 9 | } from 'graphql'; 10 | 11 | import { 12 | connectionArgs, 13 | connectionDefinitions, 14 | connectionFromArray, 15 | cursorForObjectInConnection, 16 | fromGlobalId, 17 | globalIdField, 18 | mutationWithClientMutationId, 19 | nodeDefinitions, 20 | } from 'graphql-relay'; 21 | 22 | import { 23 | User, 24 | Thread, 25 | Message, 26 | } from './data.js'; 27 | 28 | import { 29 | addMessage, 30 | getMessage, 31 | getMessagesByThreadId, 32 | getThread, 33 | getThreads, 34 | markThreadAsRead, 35 | getUser, 36 | getViewer, 37 | } from './database'; 38 | 39 | var {nodeInterface, nodeField} = nodeDefinitions( 40 | (globalId) => { 41 | var {type, id} = fromGlobalId(globalId); 42 | if (type === 'Message') { 43 | return getMessage(id); 44 | } else if (type === 'Thread') { 45 | return getThread(id); 46 | } else if (type === 'User') { 47 | return getUser(id); 48 | } 49 | return null; 50 | }, 51 | (obj) => { 52 | if (obj instanceof Message) { 53 | return GraphQLMessage; 54 | } else if (obj instanceof Thread) { 55 | return GraphQLThread; 56 | } else if (obj instanceof User) { 57 | return GraphQLUser; 58 | } 59 | return null; 60 | } 61 | ); 62 | 63 | var GraphQLMessage = new GraphQLObjectType({ 64 | name: 'Message', 65 | fields: { 66 | id: globalIdField('Message'), 67 | authorName: { 68 | type: GraphQLString, 69 | resolve: (obj) => obj.authorName 70 | }, 71 | text: { 72 | type: GraphQLString, 73 | resolve: (obj) => obj.text 74 | }, 75 | timestamp: { 76 | type: GraphQLInt, 77 | resolve: (obj) => obj.timestamp 78 | } 79 | }, 80 | interfaces: [nodeInterface] 81 | }); 82 | 83 | var { 84 | connectionType: MessageConnection, 85 | edgeType: GraphQLMessageEdge, 86 | } = connectionDefinitions({ 87 | name: 'Message', 88 | nodeType: GraphQLMessage, 89 | }); 90 | 91 | var GraphQLThread = new GraphQLObjectType({ 92 | name: 'Thread', 93 | fields: { 94 | id: globalIdField('Thread'), 95 | name: { 96 | type: GraphQLString, 97 | resolve: (obj) => obj.name 98 | }, 99 | isRead: { 100 | type: GraphQLBoolean, 101 | resolve: (obj) => obj.isRead 102 | }, 103 | messages: { 104 | type: MessageConnection, 105 | args: connectionArgs, 106 | resolve: (thread, args) => { 107 | return connectionFromArray(getMessagesByThreadId(thread.id), args); 108 | } 109 | }, 110 | lastUpdated: { 111 | type: GraphQLInt, 112 | resolve: (obj) => obj.lastUpdated 113 | } 114 | }, 115 | interfaces: [nodeInterface] 116 | }); 117 | 118 | var { connectionType: ThreadConnection } = connectionDefinitions({ 119 | name: 'Thread', 120 | nodeType: GraphQLThread, 121 | connectionFields: () => ({ 122 | unreadCount: { 123 | type: GraphQLInt, 124 | resolve: (conn) => conn.edges.filter(edge => !edge.node.isRead).length 125 | } 126 | }) 127 | }); 128 | 129 | var GraphQLUser = new GraphQLObjectType({ 130 | name: 'User', 131 | fields: { 132 | id: globalIdField('User'), 133 | threads: { 134 | type: ThreadConnection, 135 | args: connectionArgs, 136 | resolve: (obj, args) => connectionFromArray(getThreads(), args), 137 | } 138 | }, 139 | interfaces: [nodeInterface] 140 | }); 141 | 142 | var Root = new GraphQLObjectType({ 143 | name: 'Root', 144 | fields: { 145 | viewer: { 146 | type: GraphQLUser, 147 | resolve: () => getViewer() 148 | }, 149 | node: nodeField 150 | }, 151 | }); 152 | 153 | var GraphQLAddMessageMutation = mutationWithClientMutationId({ 154 | name: 'AddMessage', 155 | inputFields: { 156 | text: { type: new GraphQLNonNull(GraphQLString) }, 157 | id: { type: new GraphQLNonNull(GraphQLID) }, 158 | }, 159 | outputFields: { 160 | messageEdge: { 161 | type: GraphQLMessageEdge, 162 | resolve: ({ messageID, threadID }) => { 163 | var message = getMessage(messageID); 164 | return { 165 | cursor: cursorForObjectInConnection(getMessagesByThreadId( 166 | threadID), message), 167 | node: message, 168 | }; 169 | } 170 | }, 171 | thread: { 172 | type: GraphQLThread, 173 | resolve: ({threadID}) => getThread(threadID) 174 | }, 175 | viewer: { 176 | type: GraphQLUser, 177 | resolve: () => getViewer(), 178 | }, 179 | }, 180 | mutateAndGetPayload: ({text, id}) => { 181 | // important, else it would be encoded client id, 182 | // then database don't know how to handle 183 | var localThreadId = fromGlobalId(id).id; 184 | var {messageID, threadID} = addMessage(text, localThreadId); 185 | return {messageID, threadID}; 186 | } 187 | }); 188 | 189 | var GraphQLMarkThreadAsReadMutation = mutationWithClientMutationId({ 190 | name: 'MarkThreadAsRead', 191 | inputFields: { 192 | id: { type: new GraphQLNonNull(GraphQLID) }, 193 | }, 194 | outputFields: { 195 | thread: { 196 | type: GraphQLThread, 197 | resolve: ({localThreadId}) => getThread(localThreadId), 198 | }, 199 | viewer: { 200 | type: GraphQLUser, 201 | resolve: () => getViewer(), 202 | }, 203 | }, 204 | mutateAndGetPayload: ({id}) => { 205 | var localThreadId = fromGlobalId(id).id; 206 | markThreadAsRead(localThreadId); 207 | return {localThreadId}; 208 | }, 209 | }); 210 | 211 | var Mutation = new GraphQLObjectType({ 212 | name: 'Mutation', 213 | fields: { 214 | addMessage: GraphQLAddMessageMutation, 215 | markThreadAsRead: GraphQLMarkThreadAsReadMutation 216 | }, 217 | }); 218 | 219 | export var Schema = new GraphQLSchema({ 220 | query: Root, 221 | mutation: Mutation 222 | }); 223 | -------------------------------------------------------------------------------- /data/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "__schema": { 4 | "queryType": { 5 | "name": "Root" 6 | }, 7 | "mutationType": { 8 | "name": "Mutation" 9 | }, 10 | "types": [ 11 | { 12 | "kind": "OBJECT", 13 | "name": "Root", 14 | "description": null, 15 | "fields": [ 16 | { 17 | "name": "viewer", 18 | "description": null, 19 | "args": [], 20 | "type": { 21 | "kind": "OBJECT", 22 | "name": "User", 23 | "ofType": null 24 | }, 25 | "isDeprecated": false, 26 | "deprecationReason": null 27 | }, 28 | { 29 | "name": "node", 30 | "description": "Fetches an object given its ID", 31 | "args": [ 32 | { 33 | "name": "id", 34 | "description": "The ID of an object", 35 | "type": { 36 | "kind": "NON_NULL", 37 | "name": null, 38 | "ofType": { 39 | "kind": "SCALAR", 40 | "name": "ID", 41 | "ofType": null 42 | } 43 | }, 44 | "defaultValue": null 45 | } 46 | ], 47 | "type": { 48 | "kind": "INTERFACE", 49 | "name": "Node", 50 | "ofType": null 51 | }, 52 | "isDeprecated": false, 53 | "deprecationReason": null 54 | } 55 | ], 56 | "inputFields": null, 57 | "interfaces": [], 58 | "enumValues": null, 59 | "possibleTypes": null 60 | }, 61 | { 62 | "kind": "OBJECT", 63 | "name": "User", 64 | "description": null, 65 | "fields": [ 66 | { 67 | "name": "id", 68 | "description": "The ID of an object", 69 | "args": [], 70 | "type": { 71 | "kind": "NON_NULL", 72 | "name": null, 73 | "ofType": { 74 | "kind": "SCALAR", 75 | "name": "ID", 76 | "ofType": null 77 | } 78 | }, 79 | "isDeprecated": false, 80 | "deprecationReason": null 81 | }, 82 | { 83 | "name": "threads", 84 | "description": null, 85 | "args": [ 86 | { 87 | "name": "before", 88 | "description": null, 89 | "type": { 90 | "kind": "SCALAR", 91 | "name": "String", 92 | "ofType": null 93 | }, 94 | "defaultValue": null 95 | }, 96 | { 97 | "name": "after", 98 | "description": null, 99 | "type": { 100 | "kind": "SCALAR", 101 | "name": "String", 102 | "ofType": null 103 | }, 104 | "defaultValue": null 105 | }, 106 | { 107 | "name": "first", 108 | "description": null, 109 | "type": { 110 | "kind": "SCALAR", 111 | "name": "Int", 112 | "ofType": null 113 | }, 114 | "defaultValue": null 115 | }, 116 | { 117 | "name": "last", 118 | "description": null, 119 | "type": { 120 | "kind": "SCALAR", 121 | "name": "Int", 122 | "ofType": null 123 | }, 124 | "defaultValue": null 125 | } 126 | ], 127 | "type": { 128 | "kind": "OBJECT", 129 | "name": "ThreadConnection", 130 | "ofType": null 131 | }, 132 | "isDeprecated": false, 133 | "deprecationReason": null 134 | } 135 | ], 136 | "inputFields": null, 137 | "interfaces": [ 138 | { 139 | "kind": "INTERFACE", 140 | "name": "Node", 141 | "ofType": null 142 | } 143 | ], 144 | "enumValues": null, 145 | "possibleTypes": null 146 | }, 147 | { 148 | "kind": "INTERFACE", 149 | "name": "Node", 150 | "description": "An object with an ID", 151 | "fields": [ 152 | { 153 | "name": "id", 154 | "description": "The id of the object.", 155 | "args": [], 156 | "type": { 157 | "kind": "NON_NULL", 158 | "name": null, 159 | "ofType": { 160 | "kind": "SCALAR", 161 | "name": "ID", 162 | "ofType": null 163 | } 164 | }, 165 | "isDeprecated": false, 166 | "deprecationReason": null 167 | } 168 | ], 169 | "inputFields": null, 170 | "interfaces": null, 171 | "enumValues": null, 172 | "possibleTypes": [ 173 | { 174 | "kind": "OBJECT", 175 | "name": "Message", 176 | "ofType": null 177 | }, 178 | { 179 | "kind": "OBJECT", 180 | "name": "Thread", 181 | "ofType": null 182 | }, 183 | { 184 | "kind": "OBJECT", 185 | "name": "User", 186 | "ofType": null 187 | } 188 | ] 189 | }, 190 | { 191 | "kind": "OBJECT", 192 | "name": "Message", 193 | "description": null, 194 | "fields": [ 195 | { 196 | "name": "id", 197 | "description": "The ID of an object", 198 | "args": [], 199 | "type": { 200 | "kind": "NON_NULL", 201 | "name": null, 202 | "ofType": { 203 | "kind": "SCALAR", 204 | "name": "ID", 205 | "ofType": null 206 | } 207 | }, 208 | "isDeprecated": false, 209 | "deprecationReason": null 210 | }, 211 | { 212 | "name": "authorName", 213 | "description": null, 214 | "args": [], 215 | "type": { 216 | "kind": "SCALAR", 217 | "name": "String", 218 | "ofType": null 219 | }, 220 | "isDeprecated": false, 221 | "deprecationReason": null 222 | }, 223 | { 224 | "name": "text", 225 | "description": null, 226 | "args": [], 227 | "type": { 228 | "kind": "SCALAR", 229 | "name": "String", 230 | "ofType": null 231 | }, 232 | "isDeprecated": false, 233 | "deprecationReason": null 234 | }, 235 | { 236 | "name": "timestamp", 237 | "description": null, 238 | "args": [], 239 | "type": { 240 | "kind": "SCALAR", 241 | "name": "Int", 242 | "ofType": null 243 | }, 244 | "isDeprecated": false, 245 | "deprecationReason": null 246 | } 247 | ], 248 | "inputFields": null, 249 | "interfaces": [ 250 | { 251 | "kind": "INTERFACE", 252 | "name": "Node", 253 | "ofType": null 254 | } 255 | ], 256 | "enumValues": null, 257 | "possibleTypes": null 258 | }, 259 | { 260 | "kind": "SCALAR", 261 | "name": "ID", 262 | "description": null, 263 | "fields": null, 264 | "inputFields": null, 265 | "interfaces": null, 266 | "enumValues": null, 267 | "possibleTypes": null 268 | }, 269 | { 270 | "kind": "SCALAR", 271 | "name": "String", 272 | "description": null, 273 | "fields": null, 274 | "inputFields": null, 275 | "interfaces": null, 276 | "enumValues": null, 277 | "possibleTypes": null 278 | }, 279 | { 280 | "kind": "SCALAR", 281 | "name": "Int", 282 | "description": null, 283 | "fields": null, 284 | "inputFields": null, 285 | "interfaces": null, 286 | "enumValues": null, 287 | "possibleTypes": null 288 | }, 289 | { 290 | "kind": "OBJECT", 291 | "name": "Thread", 292 | "description": null, 293 | "fields": [ 294 | { 295 | "name": "id", 296 | "description": "The ID of an object", 297 | "args": [], 298 | "type": { 299 | "kind": "NON_NULL", 300 | "name": null, 301 | "ofType": { 302 | "kind": "SCALAR", 303 | "name": "ID", 304 | "ofType": null 305 | } 306 | }, 307 | "isDeprecated": false, 308 | "deprecationReason": null 309 | }, 310 | { 311 | "name": "name", 312 | "description": null, 313 | "args": [], 314 | "type": { 315 | "kind": "SCALAR", 316 | "name": "String", 317 | "ofType": null 318 | }, 319 | "isDeprecated": false, 320 | "deprecationReason": null 321 | }, 322 | { 323 | "name": "isRead", 324 | "description": null, 325 | "args": [], 326 | "type": { 327 | "kind": "SCALAR", 328 | "name": "Boolean", 329 | "ofType": null 330 | }, 331 | "isDeprecated": false, 332 | "deprecationReason": null 333 | }, 334 | { 335 | "name": "messages", 336 | "description": null, 337 | "args": [ 338 | { 339 | "name": "before", 340 | "description": null, 341 | "type": { 342 | "kind": "SCALAR", 343 | "name": "String", 344 | "ofType": null 345 | }, 346 | "defaultValue": null 347 | }, 348 | { 349 | "name": "after", 350 | "description": null, 351 | "type": { 352 | "kind": "SCALAR", 353 | "name": "String", 354 | "ofType": null 355 | }, 356 | "defaultValue": null 357 | }, 358 | { 359 | "name": "first", 360 | "description": null, 361 | "type": { 362 | "kind": "SCALAR", 363 | "name": "Int", 364 | "ofType": null 365 | }, 366 | "defaultValue": null 367 | }, 368 | { 369 | "name": "last", 370 | "description": null, 371 | "type": { 372 | "kind": "SCALAR", 373 | "name": "Int", 374 | "ofType": null 375 | }, 376 | "defaultValue": null 377 | } 378 | ], 379 | "type": { 380 | "kind": "OBJECT", 381 | "name": "MessageConnection", 382 | "ofType": null 383 | }, 384 | "isDeprecated": false, 385 | "deprecationReason": null 386 | }, 387 | { 388 | "name": "lastUpdated", 389 | "description": null, 390 | "args": [], 391 | "type": { 392 | "kind": "SCALAR", 393 | "name": "Int", 394 | "ofType": null 395 | }, 396 | "isDeprecated": false, 397 | "deprecationReason": null 398 | } 399 | ], 400 | "inputFields": null, 401 | "interfaces": [ 402 | { 403 | "kind": "INTERFACE", 404 | "name": "Node", 405 | "ofType": null 406 | } 407 | ], 408 | "enumValues": null, 409 | "possibleTypes": null 410 | }, 411 | { 412 | "kind": "SCALAR", 413 | "name": "Boolean", 414 | "description": null, 415 | "fields": null, 416 | "inputFields": null, 417 | "interfaces": null, 418 | "enumValues": null, 419 | "possibleTypes": null 420 | }, 421 | { 422 | "kind": "OBJECT", 423 | "name": "MessageConnection", 424 | "description": "A connection to a list of items.", 425 | "fields": [ 426 | { 427 | "name": "pageInfo", 428 | "description": "Information to aid in pagination.", 429 | "args": [], 430 | "type": { 431 | "kind": "NON_NULL", 432 | "name": null, 433 | "ofType": { 434 | "kind": "OBJECT", 435 | "name": "PageInfo", 436 | "ofType": null 437 | } 438 | }, 439 | "isDeprecated": false, 440 | "deprecationReason": null 441 | }, 442 | { 443 | "name": "edges", 444 | "description": "Information to aid in pagination.", 445 | "args": [], 446 | "type": { 447 | "kind": "LIST", 448 | "name": null, 449 | "ofType": { 450 | "kind": "OBJECT", 451 | "name": "MessageEdge", 452 | "ofType": null 453 | } 454 | }, 455 | "isDeprecated": false, 456 | "deprecationReason": null 457 | } 458 | ], 459 | "inputFields": null, 460 | "interfaces": [], 461 | "enumValues": null, 462 | "possibleTypes": null 463 | }, 464 | { 465 | "kind": "OBJECT", 466 | "name": "PageInfo", 467 | "description": "Information about pagination in a connection.", 468 | "fields": [ 469 | { 470 | "name": "hasNextPage", 471 | "description": "When paginating forwards, are there more items?", 472 | "args": [], 473 | "type": { 474 | "kind": "NON_NULL", 475 | "name": null, 476 | "ofType": { 477 | "kind": "SCALAR", 478 | "name": "Boolean", 479 | "ofType": null 480 | } 481 | }, 482 | "isDeprecated": false, 483 | "deprecationReason": null 484 | }, 485 | { 486 | "name": "hasPreviousPage", 487 | "description": "When paginating backwards, are there more items?", 488 | "args": [], 489 | "type": { 490 | "kind": "NON_NULL", 491 | "name": null, 492 | "ofType": { 493 | "kind": "SCALAR", 494 | "name": "Boolean", 495 | "ofType": null 496 | } 497 | }, 498 | "isDeprecated": false, 499 | "deprecationReason": null 500 | }, 501 | { 502 | "name": "startCursor", 503 | "description": "When paginating backwards, the cursor to continue.", 504 | "args": [], 505 | "type": { 506 | "kind": "SCALAR", 507 | "name": "String", 508 | "ofType": null 509 | }, 510 | "isDeprecated": false, 511 | "deprecationReason": null 512 | }, 513 | { 514 | "name": "endCursor", 515 | "description": "When paginating forwards, the cursor to continue.", 516 | "args": [], 517 | "type": { 518 | "kind": "SCALAR", 519 | "name": "String", 520 | "ofType": null 521 | }, 522 | "isDeprecated": false, 523 | "deprecationReason": null 524 | } 525 | ], 526 | "inputFields": null, 527 | "interfaces": [], 528 | "enumValues": null, 529 | "possibleTypes": null 530 | }, 531 | { 532 | "kind": "OBJECT", 533 | "name": "MessageEdge", 534 | "description": "An edge in a connection.", 535 | "fields": [ 536 | { 537 | "name": "node", 538 | "description": "The item at the end of the edge", 539 | "args": [], 540 | "type": { 541 | "kind": "OBJECT", 542 | "name": "Message", 543 | "ofType": null 544 | }, 545 | "isDeprecated": false, 546 | "deprecationReason": null 547 | }, 548 | { 549 | "name": "cursor", 550 | "description": "A cursor for use in pagination", 551 | "args": [], 552 | "type": { 553 | "kind": "NON_NULL", 554 | "name": null, 555 | "ofType": { 556 | "kind": "SCALAR", 557 | "name": "String", 558 | "ofType": null 559 | } 560 | }, 561 | "isDeprecated": false, 562 | "deprecationReason": null 563 | } 564 | ], 565 | "inputFields": null, 566 | "interfaces": [], 567 | "enumValues": null, 568 | "possibleTypes": null 569 | }, 570 | { 571 | "kind": "OBJECT", 572 | "name": "ThreadConnection", 573 | "description": "A connection to a list of items.", 574 | "fields": [ 575 | { 576 | "name": "pageInfo", 577 | "description": "Information to aid in pagination.", 578 | "args": [], 579 | "type": { 580 | "kind": "NON_NULL", 581 | "name": null, 582 | "ofType": { 583 | "kind": "OBJECT", 584 | "name": "PageInfo", 585 | "ofType": null 586 | } 587 | }, 588 | "isDeprecated": false, 589 | "deprecationReason": null 590 | }, 591 | { 592 | "name": "edges", 593 | "description": "Information to aid in pagination.", 594 | "args": [], 595 | "type": { 596 | "kind": "LIST", 597 | "name": null, 598 | "ofType": { 599 | "kind": "OBJECT", 600 | "name": "ThreadEdge", 601 | "ofType": null 602 | } 603 | }, 604 | "isDeprecated": false, 605 | "deprecationReason": null 606 | }, 607 | { 608 | "name": "unreadCount", 609 | "description": null, 610 | "args": [], 611 | "type": { 612 | "kind": "SCALAR", 613 | "name": "Int", 614 | "ofType": null 615 | }, 616 | "isDeprecated": false, 617 | "deprecationReason": null 618 | } 619 | ], 620 | "inputFields": null, 621 | "interfaces": [], 622 | "enumValues": null, 623 | "possibleTypes": null 624 | }, 625 | { 626 | "kind": "OBJECT", 627 | "name": "ThreadEdge", 628 | "description": "An edge in a connection.", 629 | "fields": [ 630 | { 631 | "name": "node", 632 | "description": "The item at the end of the edge", 633 | "args": [], 634 | "type": { 635 | "kind": "OBJECT", 636 | "name": "Thread", 637 | "ofType": null 638 | }, 639 | "isDeprecated": false, 640 | "deprecationReason": null 641 | }, 642 | { 643 | "name": "cursor", 644 | "description": "A cursor for use in pagination", 645 | "args": [], 646 | "type": { 647 | "kind": "NON_NULL", 648 | "name": null, 649 | "ofType": { 650 | "kind": "SCALAR", 651 | "name": "String", 652 | "ofType": null 653 | } 654 | }, 655 | "isDeprecated": false, 656 | "deprecationReason": null 657 | } 658 | ], 659 | "inputFields": null, 660 | "interfaces": [], 661 | "enumValues": null, 662 | "possibleTypes": null 663 | }, 664 | { 665 | "kind": "OBJECT", 666 | "name": "Mutation", 667 | "description": null, 668 | "fields": [ 669 | { 670 | "name": "addMessage", 671 | "description": null, 672 | "args": [ 673 | { 674 | "name": "input", 675 | "description": null, 676 | "type": { 677 | "kind": "NON_NULL", 678 | "name": null, 679 | "ofType": { 680 | "kind": "INPUT_OBJECT", 681 | "name": "AddMessageInput", 682 | "ofType": null 683 | } 684 | }, 685 | "defaultValue": null 686 | } 687 | ], 688 | "type": { 689 | "kind": "OBJECT", 690 | "name": "AddMessagePayload", 691 | "ofType": null 692 | }, 693 | "isDeprecated": false, 694 | "deprecationReason": null 695 | }, 696 | { 697 | "name": "markThreadAsRead", 698 | "description": null, 699 | "args": [ 700 | { 701 | "name": "input", 702 | "description": null, 703 | "type": { 704 | "kind": "NON_NULL", 705 | "name": null, 706 | "ofType": { 707 | "kind": "INPUT_OBJECT", 708 | "name": "MarkThreadAsReadInput", 709 | "ofType": null 710 | } 711 | }, 712 | "defaultValue": null 713 | } 714 | ], 715 | "type": { 716 | "kind": "OBJECT", 717 | "name": "MarkThreadAsReadPayload", 718 | "ofType": null 719 | }, 720 | "isDeprecated": false, 721 | "deprecationReason": null 722 | } 723 | ], 724 | "inputFields": null, 725 | "interfaces": [], 726 | "enumValues": null, 727 | "possibleTypes": null 728 | }, 729 | { 730 | "kind": "INPUT_OBJECT", 731 | "name": "AddMessageInput", 732 | "description": null, 733 | "fields": null, 734 | "inputFields": [ 735 | { 736 | "name": "text", 737 | "description": null, 738 | "type": { 739 | "kind": "NON_NULL", 740 | "name": null, 741 | "ofType": { 742 | "kind": "SCALAR", 743 | "name": "String", 744 | "ofType": null 745 | } 746 | }, 747 | "defaultValue": null 748 | }, 749 | { 750 | "name": "id", 751 | "description": null, 752 | "type": { 753 | "kind": "NON_NULL", 754 | "name": null, 755 | "ofType": { 756 | "kind": "SCALAR", 757 | "name": "ID", 758 | "ofType": null 759 | } 760 | }, 761 | "defaultValue": null 762 | }, 763 | { 764 | "name": "clientMutationId", 765 | "description": null, 766 | "type": { 767 | "kind": "NON_NULL", 768 | "name": null, 769 | "ofType": { 770 | "kind": "SCALAR", 771 | "name": "String", 772 | "ofType": null 773 | } 774 | }, 775 | "defaultValue": null 776 | } 777 | ], 778 | "interfaces": null, 779 | "enumValues": null, 780 | "possibleTypes": null 781 | }, 782 | { 783 | "kind": "OBJECT", 784 | "name": "AddMessagePayload", 785 | "description": null, 786 | "fields": [ 787 | { 788 | "name": "messageEdge", 789 | "description": null, 790 | "args": [], 791 | "type": { 792 | "kind": "OBJECT", 793 | "name": "MessageEdge", 794 | "ofType": null 795 | }, 796 | "isDeprecated": false, 797 | "deprecationReason": null 798 | }, 799 | { 800 | "name": "thread", 801 | "description": null, 802 | "args": [], 803 | "type": { 804 | "kind": "OBJECT", 805 | "name": "Thread", 806 | "ofType": null 807 | }, 808 | "isDeprecated": false, 809 | "deprecationReason": null 810 | }, 811 | { 812 | "name": "viewer", 813 | "description": null, 814 | "args": [], 815 | "type": { 816 | "kind": "OBJECT", 817 | "name": "User", 818 | "ofType": null 819 | }, 820 | "isDeprecated": false, 821 | "deprecationReason": null 822 | }, 823 | { 824 | "name": "clientMutationId", 825 | "description": null, 826 | "args": [], 827 | "type": { 828 | "kind": "NON_NULL", 829 | "name": null, 830 | "ofType": { 831 | "kind": "SCALAR", 832 | "name": "String", 833 | "ofType": null 834 | } 835 | }, 836 | "isDeprecated": false, 837 | "deprecationReason": null 838 | } 839 | ], 840 | "inputFields": null, 841 | "interfaces": [], 842 | "enumValues": null, 843 | "possibleTypes": null 844 | }, 845 | { 846 | "kind": "INPUT_OBJECT", 847 | "name": "MarkThreadAsReadInput", 848 | "description": null, 849 | "fields": null, 850 | "inputFields": [ 851 | { 852 | "name": "id", 853 | "description": null, 854 | "type": { 855 | "kind": "NON_NULL", 856 | "name": null, 857 | "ofType": { 858 | "kind": "SCALAR", 859 | "name": "ID", 860 | "ofType": null 861 | } 862 | }, 863 | "defaultValue": null 864 | }, 865 | { 866 | "name": "clientMutationId", 867 | "description": null, 868 | "type": { 869 | "kind": "NON_NULL", 870 | "name": null, 871 | "ofType": { 872 | "kind": "SCALAR", 873 | "name": "String", 874 | "ofType": null 875 | } 876 | }, 877 | "defaultValue": null 878 | } 879 | ], 880 | "interfaces": null, 881 | "enumValues": null, 882 | "possibleTypes": null 883 | }, 884 | { 885 | "kind": "OBJECT", 886 | "name": "MarkThreadAsReadPayload", 887 | "description": null, 888 | "fields": [ 889 | { 890 | "name": "thread", 891 | "description": null, 892 | "args": [], 893 | "type": { 894 | "kind": "OBJECT", 895 | "name": "Thread", 896 | "ofType": null 897 | }, 898 | "isDeprecated": false, 899 | "deprecationReason": null 900 | }, 901 | { 902 | "name": "viewer", 903 | "description": null, 904 | "args": [], 905 | "type": { 906 | "kind": "OBJECT", 907 | "name": "User", 908 | "ofType": null 909 | }, 910 | "isDeprecated": false, 911 | "deprecationReason": null 912 | }, 913 | { 914 | "name": "clientMutationId", 915 | "description": null, 916 | "args": [], 917 | "type": { 918 | "kind": "NON_NULL", 919 | "name": null, 920 | "ofType": { 921 | "kind": "SCALAR", 922 | "name": "String", 923 | "ofType": null 924 | } 925 | }, 926 | "isDeprecated": false, 927 | "deprecationReason": null 928 | } 929 | ], 930 | "inputFields": null, 931 | "interfaces": [], 932 | "enumValues": null, 933 | "possibleTypes": null 934 | }, 935 | { 936 | "kind": "OBJECT", 937 | "name": "__Schema", 938 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query and mutation operations.", 939 | "fields": [ 940 | { 941 | "name": "types", 942 | "description": "A list of all types supported by this server.", 943 | "args": [], 944 | "type": { 945 | "kind": "NON_NULL", 946 | "name": null, 947 | "ofType": { 948 | "kind": "LIST", 949 | "name": null, 950 | "ofType": { 951 | "kind": "NON_NULL", 952 | "name": null, 953 | "ofType": { 954 | "kind": "OBJECT", 955 | "name": "__Type" 956 | } 957 | } 958 | } 959 | }, 960 | "isDeprecated": false, 961 | "deprecationReason": null 962 | }, 963 | { 964 | "name": "queryType", 965 | "description": "The type that query operations will be rooted at.", 966 | "args": [], 967 | "type": { 968 | "kind": "NON_NULL", 969 | "name": null, 970 | "ofType": { 971 | "kind": "OBJECT", 972 | "name": "__Type", 973 | "ofType": null 974 | } 975 | }, 976 | "isDeprecated": false, 977 | "deprecationReason": null 978 | }, 979 | { 980 | "name": "mutationType", 981 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 982 | "args": [], 983 | "type": { 984 | "kind": "OBJECT", 985 | "name": "__Type", 986 | "ofType": null 987 | }, 988 | "isDeprecated": false, 989 | "deprecationReason": null 990 | }, 991 | { 992 | "name": "directives", 993 | "description": "A list of all directives supported by this server.", 994 | "args": [], 995 | "type": { 996 | "kind": "NON_NULL", 997 | "name": null, 998 | "ofType": { 999 | "kind": "LIST", 1000 | "name": null, 1001 | "ofType": { 1002 | "kind": "NON_NULL", 1003 | "name": null, 1004 | "ofType": { 1005 | "kind": "OBJECT", 1006 | "name": "__Directive" 1007 | } 1008 | } 1009 | } 1010 | }, 1011 | "isDeprecated": false, 1012 | "deprecationReason": null 1013 | } 1014 | ], 1015 | "inputFields": null, 1016 | "interfaces": [], 1017 | "enumValues": null, 1018 | "possibleTypes": null 1019 | }, 1020 | { 1021 | "kind": "OBJECT", 1022 | "name": "__Type", 1023 | "description": null, 1024 | "fields": [ 1025 | { 1026 | "name": "kind", 1027 | "description": null, 1028 | "args": [], 1029 | "type": { 1030 | "kind": "NON_NULL", 1031 | "name": null, 1032 | "ofType": { 1033 | "kind": "ENUM", 1034 | "name": "__TypeKind", 1035 | "ofType": null 1036 | } 1037 | }, 1038 | "isDeprecated": false, 1039 | "deprecationReason": null 1040 | }, 1041 | { 1042 | "name": "name", 1043 | "description": null, 1044 | "args": [], 1045 | "type": { 1046 | "kind": "SCALAR", 1047 | "name": "String", 1048 | "ofType": null 1049 | }, 1050 | "isDeprecated": false, 1051 | "deprecationReason": null 1052 | }, 1053 | { 1054 | "name": "description", 1055 | "description": null, 1056 | "args": [], 1057 | "type": { 1058 | "kind": "SCALAR", 1059 | "name": "String", 1060 | "ofType": null 1061 | }, 1062 | "isDeprecated": false, 1063 | "deprecationReason": null 1064 | }, 1065 | { 1066 | "name": "fields", 1067 | "description": null, 1068 | "args": [ 1069 | { 1070 | "name": "includeDeprecated", 1071 | "description": null, 1072 | "type": { 1073 | "kind": "SCALAR", 1074 | "name": "Boolean", 1075 | "ofType": null 1076 | }, 1077 | "defaultValue": "false" 1078 | } 1079 | ], 1080 | "type": { 1081 | "kind": "LIST", 1082 | "name": null, 1083 | "ofType": { 1084 | "kind": "NON_NULL", 1085 | "name": null, 1086 | "ofType": { 1087 | "kind": "OBJECT", 1088 | "name": "__Field", 1089 | "ofType": null 1090 | } 1091 | } 1092 | }, 1093 | "isDeprecated": false, 1094 | "deprecationReason": null 1095 | }, 1096 | { 1097 | "name": "interfaces", 1098 | "description": null, 1099 | "args": [], 1100 | "type": { 1101 | "kind": "LIST", 1102 | "name": null, 1103 | "ofType": { 1104 | "kind": "NON_NULL", 1105 | "name": null, 1106 | "ofType": { 1107 | "kind": "OBJECT", 1108 | "name": "__Type", 1109 | "ofType": null 1110 | } 1111 | } 1112 | }, 1113 | "isDeprecated": false, 1114 | "deprecationReason": null 1115 | }, 1116 | { 1117 | "name": "possibleTypes", 1118 | "description": null, 1119 | "args": [], 1120 | "type": { 1121 | "kind": "LIST", 1122 | "name": null, 1123 | "ofType": { 1124 | "kind": "NON_NULL", 1125 | "name": null, 1126 | "ofType": { 1127 | "kind": "OBJECT", 1128 | "name": "__Type", 1129 | "ofType": null 1130 | } 1131 | } 1132 | }, 1133 | "isDeprecated": false, 1134 | "deprecationReason": null 1135 | }, 1136 | { 1137 | "name": "enumValues", 1138 | "description": null, 1139 | "args": [ 1140 | { 1141 | "name": "includeDeprecated", 1142 | "description": null, 1143 | "type": { 1144 | "kind": "SCALAR", 1145 | "name": "Boolean", 1146 | "ofType": null 1147 | }, 1148 | "defaultValue": "false" 1149 | } 1150 | ], 1151 | "type": { 1152 | "kind": "LIST", 1153 | "name": null, 1154 | "ofType": { 1155 | "kind": "NON_NULL", 1156 | "name": null, 1157 | "ofType": { 1158 | "kind": "OBJECT", 1159 | "name": "__EnumValue", 1160 | "ofType": null 1161 | } 1162 | } 1163 | }, 1164 | "isDeprecated": false, 1165 | "deprecationReason": null 1166 | }, 1167 | { 1168 | "name": "inputFields", 1169 | "description": null, 1170 | "args": [], 1171 | "type": { 1172 | "kind": "LIST", 1173 | "name": null, 1174 | "ofType": { 1175 | "kind": "NON_NULL", 1176 | "name": null, 1177 | "ofType": { 1178 | "kind": "OBJECT", 1179 | "name": "__InputValue", 1180 | "ofType": null 1181 | } 1182 | } 1183 | }, 1184 | "isDeprecated": false, 1185 | "deprecationReason": null 1186 | }, 1187 | { 1188 | "name": "ofType", 1189 | "description": null, 1190 | "args": [], 1191 | "type": { 1192 | "kind": "OBJECT", 1193 | "name": "__Type", 1194 | "ofType": null 1195 | }, 1196 | "isDeprecated": false, 1197 | "deprecationReason": null 1198 | } 1199 | ], 1200 | "inputFields": null, 1201 | "interfaces": [], 1202 | "enumValues": null, 1203 | "possibleTypes": null 1204 | }, 1205 | { 1206 | "kind": "ENUM", 1207 | "name": "__TypeKind", 1208 | "description": "An enum describing what kind of type a given __Type is", 1209 | "fields": null, 1210 | "inputFields": null, 1211 | "interfaces": null, 1212 | "enumValues": [ 1213 | { 1214 | "name": "SCALAR", 1215 | "description": "Indicates this type is a scalar.", 1216 | "isDeprecated": false, 1217 | "deprecationReason": null 1218 | }, 1219 | { 1220 | "name": "OBJECT", 1221 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 1222 | "isDeprecated": false, 1223 | "deprecationReason": null 1224 | }, 1225 | { 1226 | "name": "INTERFACE", 1227 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 1228 | "isDeprecated": false, 1229 | "deprecationReason": null 1230 | }, 1231 | { 1232 | "name": "UNION", 1233 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 1234 | "isDeprecated": false, 1235 | "deprecationReason": null 1236 | }, 1237 | { 1238 | "name": "ENUM", 1239 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 1240 | "isDeprecated": false, 1241 | "deprecationReason": null 1242 | }, 1243 | { 1244 | "name": "INPUT_OBJECT", 1245 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 1246 | "isDeprecated": false, 1247 | "deprecationReason": null 1248 | }, 1249 | { 1250 | "name": "LIST", 1251 | "description": "Indicates this type is a list. `ofType` is a valid field.", 1252 | "isDeprecated": false, 1253 | "deprecationReason": null 1254 | }, 1255 | { 1256 | "name": "NON_NULL", 1257 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 1258 | "isDeprecated": false, 1259 | "deprecationReason": null 1260 | } 1261 | ], 1262 | "possibleTypes": null 1263 | }, 1264 | { 1265 | "kind": "OBJECT", 1266 | "name": "__Field", 1267 | "description": null, 1268 | "fields": [ 1269 | { 1270 | "name": "name", 1271 | "description": null, 1272 | "args": [], 1273 | "type": { 1274 | "kind": "NON_NULL", 1275 | "name": null, 1276 | "ofType": { 1277 | "kind": "SCALAR", 1278 | "name": "String", 1279 | "ofType": null 1280 | } 1281 | }, 1282 | "isDeprecated": false, 1283 | "deprecationReason": null 1284 | }, 1285 | { 1286 | "name": "description", 1287 | "description": null, 1288 | "args": [], 1289 | "type": { 1290 | "kind": "SCALAR", 1291 | "name": "String", 1292 | "ofType": null 1293 | }, 1294 | "isDeprecated": false, 1295 | "deprecationReason": null 1296 | }, 1297 | { 1298 | "name": "args", 1299 | "description": null, 1300 | "args": [], 1301 | "type": { 1302 | "kind": "NON_NULL", 1303 | "name": null, 1304 | "ofType": { 1305 | "kind": "LIST", 1306 | "name": null, 1307 | "ofType": { 1308 | "kind": "NON_NULL", 1309 | "name": null, 1310 | "ofType": { 1311 | "kind": "OBJECT", 1312 | "name": "__InputValue" 1313 | } 1314 | } 1315 | } 1316 | }, 1317 | "isDeprecated": false, 1318 | "deprecationReason": null 1319 | }, 1320 | { 1321 | "name": "type", 1322 | "description": null, 1323 | "args": [], 1324 | "type": { 1325 | "kind": "NON_NULL", 1326 | "name": null, 1327 | "ofType": { 1328 | "kind": "OBJECT", 1329 | "name": "__Type", 1330 | "ofType": null 1331 | } 1332 | }, 1333 | "isDeprecated": false, 1334 | "deprecationReason": null 1335 | }, 1336 | { 1337 | "name": "isDeprecated", 1338 | "description": null, 1339 | "args": [], 1340 | "type": { 1341 | "kind": "NON_NULL", 1342 | "name": null, 1343 | "ofType": { 1344 | "kind": "SCALAR", 1345 | "name": "Boolean", 1346 | "ofType": null 1347 | } 1348 | }, 1349 | "isDeprecated": false, 1350 | "deprecationReason": null 1351 | }, 1352 | { 1353 | "name": "deprecationReason", 1354 | "description": null, 1355 | "args": [], 1356 | "type": { 1357 | "kind": "SCALAR", 1358 | "name": "String", 1359 | "ofType": null 1360 | }, 1361 | "isDeprecated": false, 1362 | "deprecationReason": null 1363 | } 1364 | ], 1365 | "inputFields": null, 1366 | "interfaces": [], 1367 | "enumValues": null, 1368 | "possibleTypes": null 1369 | }, 1370 | { 1371 | "kind": "OBJECT", 1372 | "name": "__InputValue", 1373 | "description": null, 1374 | "fields": [ 1375 | { 1376 | "name": "name", 1377 | "description": null, 1378 | "args": [], 1379 | "type": { 1380 | "kind": "NON_NULL", 1381 | "name": null, 1382 | "ofType": { 1383 | "kind": "SCALAR", 1384 | "name": "String", 1385 | "ofType": null 1386 | } 1387 | }, 1388 | "isDeprecated": false, 1389 | "deprecationReason": null 1390 | }, 1391 | { 1392 | "name": "description", 1393 | "description": null, 1394 | "args": [], 1395 | "type": { 1396 | "kind": "SCALAR", 1397 | "name": "String", 1398 | "ofType": null 1399 | }, 1400 | "isDeprecated": false, 1401 | "deprecationReason": null 1402 | }, 1403 | { 1404 | "name": "type", 1405 | "description": null, 1406 | "args": [], 1407 | "type": { 1408 | "kind": "NON_NULL", 1409 | "name": null, 1410 | "ofType": { 1411 | "kind": "OBJECT", 1412 | "name": "__Type", 1413 | "ofType": null 1414 | } 1415 | }, 1416 | "isDeprecated": false, 1417 | "deprecationReason": null 1418 | }, 1419 | { 1420 | "name": "defaultValue", 1421 | "description": null, 1422 | "args": [], 1423 | "type": { 1424 | "kind": "SCALAR", 1425 | "name": "String", 1426 | "ofType": null 1427 | }, 1428 | "isDeprecated": false, 1429 | "deprecationReason": null 1430 | } 1431 | ], 1432 | "inputFields": null, 1433 | "interfaces": [], 1434 | "enumValues": null, 1435 | "possibleTypes": null 1436 | }, 1437 | { 1438 | "kind": "OBJECT", 1439 | "name": "__EnumValue", 1440 | "description": null, 1441 | "fields": [ 1442 | { 1443 | "name": "name", 1444 | "description": null, 1445 | "args": [], 1446 | "type": { 1447 | "kind": "NON_NULL", 1448 | "name": null, 1449 | "ofType": { 1450 | "kind": "SCALAR", 1451 | "name": "String", 1452 | "ofType": null 1453 | } 1454 | }, 1455 | "isDeprecated": false, 1456 | "deprecationReason": null 1457 | }, 1458 | { 1459 | "name": "description", 1460 | "description": null, 1461 | "args": [], 1462 | "type": { 1463 | "kind": "SCALAR", 1464 | "name": "String", 1465 | "ofType": null 1466 | }, 1467 | "isDeprecated": false, 1468 | "deprecationReason": null 1469 | }, 1470 | { 1471 | "name": "isDeprecated", 1472 | "description": null, 1473 | "args": [], 1474 | "type": { 1475 | "kind": "NON_NULL", 1476 | "name": null, 1477 | "ofType": { 1478 | "kind": "SCALAR", 1479 | "name": "Boolean", 1480 | "ofType": null 1481 | } 1482 | }, 1483 | "isDeprecated": false, 1484 | "deprecationReason": null 1485 | }, 1486 | { 1487 | "name": "deprecationReason", 1488 | "description": null, 1489 | "args": [], 1490 | "type": { 1491 | "kind": "SCALAR", 1492 | "name": "String", 1493 | "ofType": null 1494 | }, 1495 | "isDeprecated": false, 1496 | "deprecationReason": null 1497 | } 1498 | ], 1499 | "inputFields": null, 1500 | "interfaces": [], 1501 | "enumValues": null, 1502 | "possibleTypes": null 1503 | }, 1504 | { 1505 | "kind": "OBJECT", 1506 | "name": "__Directive", 1507 | "description": null, 1508 | "fields": [ 1509 | { 1510 | "name": "name", 1511 | "description": null, 1512 | "args": [], 1513 | "type": { 1514 | "kind": "NON_NULL", 1515 | "name": null, 1516 | "ofType": { 1517 | "kind": "SCALAR", 1518 | "name": "String", 1519 | "ofType": null 1520 | } 1521 | }, 1522 | "isDeprecated": false, 1523 | "deprecationReason": null 1524 | }, 1525 | { 1526 | "name": "description", 1527 | "description": null, 1528 | "args": [], 1529 | "type": { 1530 | "kind": "SCALAR", 1531 | "name": "String", 1532 | "ofType": null 1533 | }, 1534 | "isDeprecated": false, 1535 | "deprecationReason": null 1536 | }, 1537 | { 1538 | "name": "args", 1539 | "description": null, 1540 | "args": [], 1541 | "type": { 1542 | "kind": "NON_NULL", 1543 | "name": null, 1544 | "ofType": { 1545 | "kind": "LIST", 1546 | "name": null, 1547 | "ofType": { 1548 | "kind": "NON_NULL", 1549 | "name": null, 1550 | "ofType": { 1551 | "kind": "OBJECT", 1552 | "name": "__InputValue" 1553 | } 1554 | } 1555 | } 1556 | }, 1557 | "isDeprecated": false, 1558 | "deprecationReason": null 1559 | }, 1560 | { 1561 | "name": "onOperation", 1562 | "description": null, 1563 | "args": [], 1564 | "type": { 1565 | "kind": "NON_NULL", 1566 | "name": null, 1567 | "ofType": { 1568 | "kind": "SCALAR", 1569 | "name": "Boolean", 1570 | "ofType": null 1571 | } 1572 | }, 1573 | "isDeprecated": false, 1574 | "deprecationReason": null 1575 | }, 1576 | { 1577 | "name": "onFragment", 1578 | "description": null, 1579 | "args": [], 1580 | "type": { 1581 | "kind": "NON_NULL", 1582 | "name": null, 1583 | "ofType": { 1584 | "kind": "SCALAR", 1585 | "name": "Boolean", 1586 | "ofType": null 1587 | } 1588 | }, 1589 | "isDeprecated": false, 1590 | "deprecationReason": null 1591 | }, 1592 | { 1593 | "name": "onField", 1594 | "description": null, 1595 | "args": [], 1596 | "type": { 1597 | "kind": "NON_NULL", 1598 | "name": null, 1599 | "ofType": { 1600 | "kind": "SCALAR", 1601 | "name": "Boolean", 1602 | "ofType": null 1603 | } 1604 | }, 1605 | "isDeprecated": false, 1606 | "deprecationReason": null 1607 | } 1608 | ], 1609 | "inputFields": null, 1610 | "interfaces": [], 1611 | "enumValues": null, 1612 | "possibleTypes": null 1613 | } 1614 | ], 1615 | "directives": [ 1616 | { 1617 | "name": "include", 1618 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 1619 | "args": [ 1620 | { 1621 | "name": "if", 1622 | "description": "Included when true.", 1623 | "type": { 1624 | "kind": "NON_NULL", 1625 | "name": null, 1626 | "ofType": { 1627 | "kind": "SCALAR", 1628 | "name": "Boolean", 1629 | "ofType": null 1630 | } 1631 | }, 1632 | "defaultValue": null 1633 | } 1634 | ], 1635 | "onOperation": false, 1636 | "onFragment": true, 1637 | "onField": true 1638 | }, 1639 | { 1640 | "name": "skip", 1641 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 1642 | "args": [ 1643 | { 1644 | "name": "if", 1645 | "description": "Skipped when true.", 1646 | "type": { 1647 | "kind": "NON_NULL", 1648 | "name": null, 1649 | "ofType": { 1650 | "kind": "SCALAR", 1651 | "name": "Boolean", 1652 | "ofType": null 1653 | } 1654 | }, 1655 | "defaultValue": null 1656 | } 1657 | ], 1658 | "onOperation": false, 1659 | "onFragment": true, 1660 | "onField": true 1661 | } 1662 | ] 1663 | } 1664 | } 1665 | } -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {Router} from 'react-router'; 4 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 5 | import Routes from './routes'; 6 | import ReactRouterRelay from 'react-router-relay'; 7 | 8 | ReactDOM.render( 9 | 13 | {Routes} 14 | , 15 | document.getElementById('root') 16 | ); 17 | -------------------------------------------------------------------------------- /js/components/ChatApp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is provided by Facebook for testing and evaluation purposes 3 | * only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 9 | * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import React from 'react'; 14 | import Relay from 'react-relay'; 15 | import { PropTypes } from 'react-router'; 16 | import ThreadSection from './ThreadSection'; 17 | import MessageSection from './MessageSection'; 18 | 19 | class ChatApp extends React.Component { 20 | 21 | static contextTypes = { 22 | history: PropTypes.history, 23 | } 24 | 25 | componentWillMount() { 26 | // by default, set threads[0].id to currentID if pathname === '/' 27 | // 如果 route 是'/'的時候自動把 route 轉到 threads[0] 的 id 對應到的 28 | // 'thread/:id' 去,因為目前無法在更上層 access 到 這個 id, 所以先這樣做 29 | // TODO: better if we can do it in route config 30 | const currentThreadID = this.props.viewer.threads.edges[0].node.id; 31 | if (window.location.pathname === '/' ) { 32 | this.context.history.pushState(null, `/thread/${currentThreadID}`); 33 | } 34 | } 35 | 36 | render() { 37 | // the specified fragments below would be this.props here 38 | // Note: Relay ask you to pass props(ex: thread) to child component 39 | // if it need to use this fragement(thread), think it as corresponding 40 | // ThreadSection.getFragment... belowing 41 | // 下面 specify 的 fragments 會變成這裡的 this.props 42 | // 注意: Relay 要求你在下層(ex: ThreadSection)用到的 fragments 43 | // 這裡要當 props 傳下去, 可以想成要跟 ThreadSection.getFragment...對應到 44 | const {viewer, viewer: {threads}} = this.props; 45 | return ( 46 |
47 | 48 | {this.props.children} 49 |
50 | ); 51 | } 52 | 53 | } 54 | 55 | export default Relay.createContainer(ChatApp, { 56 | fragments: { 57 | // Note: Relay will ask you to specify first, last, before, or after 58 | // if you need to query to edges, so we set a big number here 59 | // 注意:Relay 會要你 specify specify first, last, before, 或 after 60 | // 如果你要 query 任何 edges 裡的東西, 所以我們這裡假定一個很大的數 61 | viewer: () => Relay.QL` 62 | fragment on User { 63 | threads(first: 9007199254740991) { 64 | edges { 65 | node { 66 | id, 67 | }, 68 | }, 69 | ${ThreadSection.getFragment('threads')} 70 | }, 71 | ${ThreadSection.getFragment('viewer')}, 72 | ${MessageSection.getFragment('viewer')} 73 | } 74 | ` 75 | }, 76 | }); 77 | -------------------------------------------------------------------------------- /js/components/MessageComposer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is provided by Facebook for testing and evaluation purposes 3 | * only. Facebook reserves all rights not expressly granted. 4 | * 5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 9 | * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | import React from 'react'; 14 | import Relay from 'react-relay'; 15 | import AddMessageMutation from '../mutations/AddMessageMutation'; 16 | 17 | var ENTER_KEY_CODE = 13; 18 | 19 | class MessageComposer extends React.Component { 20 | 21 | constructor(props, context) { 22 | super(props, context); 23 | this.state = {text: ''}; 24 | } 25 | 26 | render() { 27 | return ( 28 |