Browse Source

CoffeeScript all the things

master
Josh Habdas 6 years ago
parent
commit
633cc8fa4d
21 changed files with 480 additions and 14 deletions
  1. +12
    -0
      .editorconfig
  2. +4
    -0
      app/actions/messages.coffee
  3. +6
    -0
      app/actions/threads.coffee
  4. +0
    -1
      app/assets/README.md
  5. +5
    -4
      app/assets/index.html
  6. +10
    -0
      app/components/App.cjsx
  7. +26
    -0
      app/components/MessageComposer.cjsx
  8. +13
    -0
      app/components/MessageListItem.cjsx
  9. +44
    -0
      app/components/MessageSection.cjsx
  10. +37
    -0
      app/components/ThreadListItem.cjsx
  11. +26
    -0
      app/components/ThreadSection.cjsx
  12. +7
    -0
      app/initialize.coffee
  13. +0
    -1
      app/initialize.js
  14. +79
    -0
      app/lib/utils.coffee
  15. +8
    -0
      app/routes.cjsx
  16. +45
    -0
      app/stores/messages.coffee
  17. +42
    -0
      app/stores/threads.coffee
  18. +98
    -0
      app/styles/app.css
  19. +11
    -3
      bower.json
  20. +5
    -5
      brunch-config.coffee
  21. +2
    -0
      package.json

+ 12
- 0
.editorconfig View File

@ -0,0 +1,12 @@
# For more information visit http://editorconfig.org/
# Top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file.
# Also 2 space indentation.
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

+ 4
- 0
app/actions/messages.coffee View File

@ -0,0 +1,4 @@
module.exports = Exim.createActions [
'recieveMessages'
'createMessage'
]

+ 6
- 0
app/actions/threads.coffee View File

@ -0,0 +1,6 @@
module.exports = Exim.createActions [
'recieveThreads'
'updateCurrent'
'updateLast'
'updateUnread'
]

+ 0
- 1
app/assets/README.md View File

@ -1 +0,0 @@
Files here will be copied as-is to public directory.

+ 5
- 4
app/assets/index.html View File

@ -4,11 +4,12 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Brunch example application</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="/stylesheets/app.css">
<script src="/javascripts/vendor.js"></script>
<script src="/javascripts/app.js"></script>
<meta name="apple-mobile-web-app-title" content="BWP Flux">
<title>BWP Flux</title>
<link rel="stylesheet" href="/styles/app.css">
<script src="/scripts/vendor.js"></script>
<script src="/scripts/app.js"></script>
<script>require('initialize');</script>
</head>
<body>

+ 10
- 0
app/components/App.cjsx View File

@ -0,0 +1,10 @@
ThreadSection = require("./ThreadSection")
MessageSection = require("./MessageSection")
RouteHandler = Exim.Router.RouteHandler
module.exports = Exim.createView
render: ->
<div className="chatapp">
<ThreadSection />
<RouteHandler />
</div>

+ 26
- 0
app/components/MessageComposer.cjsx View File

@ -0,0 +1,26 @@
ENTER_KEY_CODE = 13
actions = require('actions/messages')
module.exports = Exim.createView
getInitialState: ->
text: ''
onChange: (evt, value) ->
@setState({text: evt.target.value})
onKeyDown: (evt) ->
if evt.keyCode == ENTER_KEY_CODE
evt.preventDefault()
text = @state.text.trim()
if text
actions.createMessage(text)
@setState({text: ''})
render: ->
<textarea
className="message-composer"
name="message"
value={@state.text}
onChange={@onChange}
onKeyDown={@onKeyDown}
/>

+ 13
- 0
app/components/MessageListItem.cjsx View File

@ -0,0 +1,13 @@
module.exports = Exim.createView
propTypes:
message: React.PropTypes.object
render: ->
msg = @props.message
<li className="message-list-item">
<h5 className="message-author-name">{msg.authorName}</h5>
<div className="message-time">
{msg.date.toLocaleTimeString()}
</div>
<div className="message-text">{msg.text}</div>
</li>

+ 44
- 0
app/components/MessageSection.cjsx View File

@ -0,0 +1,44 @@
MessageListItem = require('./MessageListItem')
MessageComposer = require('./MessageComposer')
MessageActions = require('actions/messages')
MessageStore = require('stores/messages')
ThreadActions = require('actions/threads')
ThreadStore = require('stores/threads')
module.exports = Exim.createView
mixins: [
Exim.Router.State
Exim.connect(MessageStore, 'messages')
Exim.connect(ThreadStore, 'currentID')
]
statics:
willTransitionTo: (transition, params) ->
if (params && params.id)
ThreadActions.updateCurrent(params.id)
MessageActions.recieveMessages()
componentDidUpdate: ->
@scrollToBottom()
updateUnread: ->
id = @getParams().id
thread = ThreadStore.get('threads')[id]
if (!thread.lastMessage.isRead)
ThreadActions.updateUnread(id)
scrollToBottom: ->
list = @refs.messageList.getDOMNode()
list.scrollTop = list.scrollHeight
render: ->
messageListItems = @state.messages.map (message) ->
<MessageListItem key={message.id} message={message} />
<div className="message-section" onMouseMove={@updateUnread}>
<h3 className="message-thread-heading">{@state.current}</h3>
<ul className="message-list" ref="messageList">
{messageListItems}
</ul>
<MessageComposer />
</div>

+ 37
- 0
app/components/ThreadListItem.cjsx View File

@ -0,0 +1,37 @@
Link = Exim.Router.Link
State = Exim.Router.State
ListItem = Exim.createView
mixins: [State]
render: ->
isActive = @isActive(@props.to, @props.params, @props.query)
className = (if isActive then ' active ' else '')
if cls = @props.className
className += cls
@props.className = ''
link = Link(@props)
<li className={className}>{link}</li>
module.exports = Exim.createView
mixins: [State]
propTypes:
thread: React.PropTypes.object
currentThreadID: React.PropTypes.string
render: ->
thread = @props.thread
lastMessage = thread.lastMessage
className = Exim.cx
'thread-list-item': true
unread: thread.unread
<ListItem className={className} to="message" params={{id: thread.id}}>
<h5 className="thread-name">{thread.name}</h5>
<div className="thread-time">
{lastMessage.date.toLocaleTimeString()}
</div>
<div className="thread-last-message">
{lastMessage.text}
</div>
</ListItem>

+ 26
- 0
app/components/ThreadSection.cjsx View File

@ -0,0 +1,26 @@
ThreadListItem = require('./ThreadListItem')
actions = require('actions/threads')
Store = require('stores/threads')
module.exports = React.createClass
mixins: [Exim.connect(Store, 'threads', 'unread')]
componentWillMount: ->
actions.recieveThreads()
render: ->
threads = @state.threads
items = Object.keys(threads).sort((a, b) ->
threads[b].lastMessage.date - threads[a].lastMessage.date if threads[b].lastMessage and threads[a].lastMessage
).map (threadID) ->
thread = threads[threadID]
<ThreadListItem key={threadID} thread={thread} />
<div className="thread-section">
<div className="thread-count">
<span><b>Unread threads:</b> {this.state.unread}</span>
</div>
<ul className="thread-list">
{items}
</ul>
</div>

+ 7
- 0
app/initialize.coffee View File

@ -0,0 +1,7 @@
routes = require('routes')
utils = require('lib/utils')
document.addEventListener 'DOMContentLoaded', ->
utils.initLocalStorage()
Exim.Router.startRouting routes, document.body
, true

+ 0
- 1
app/initialize.js View File

@ -1 +0,0 @@
console.log('Yo');

+ 79
- 0
app/lib/utils.coffee View File

@ -0,0 +1,79 @@
utils = {}
utils.transform = (constants, mappings) ->
Object.keys(mappings).map (key) ->
[constants[key], mappings[key]]
utils.getAndParse = (name) ->
JSON.parse localStorage.getItem name
utils.dateComparator = (first, second) ->
first.date - second.date
utils.dateSetter = (message) ->
message.date = new Date(message.timestamp)
message
utils.getThreads = (messages) ->
threads = {}
for message in messages
threads[message.threadID] =
id: message.threadID
name: message.threadName
lastMessage: message
threads
utils.initLocalStorage = ->
localStorage.clear()
localStorage.setItem "messages", JSON.stringify [
id: "m_1"
threadID: "t_1"
threadName: "Jack and Jill"
authorName: "Jill"
text: "Hey Jack, want to give a Flux talk at ForwardJS?"
timestamp: Date.now() - 99999
,
id: "m_2"
threadID: "t_1"
threadName: "Jack and Jill"
authorName: "Jill"
text: "Seems like a pretty cool conference."
timestamp: Date.now() - 89999
,
id: "m_3"
threadID: "t_1"
threadName: "Jack and Jill"
authorName: "Jack"
text: "Sounds good. Will they be serving dessert?"
timestamp: Date.now() - 79999
,
id: "m_4"
threadID: "t_2"
threadName: "Paul and Jill"
authorName: "Jill"
text: "Hey Paul, want to get a beer after the conference?"
timestamp: Date.now() - 69999
,
id: "m_5"
threadID: "t_2"
threadName: "Paul and Jill"
authorName: "Paul"
text: "Totally! Meet you at the hotel bar."
timestamp: Date.now() - 59999
,
id: "m_6"
threadID: "t_3"
threadName: "Functional Heads"
authorName: "Jill"
text: "Hey Brian, are you going to be talking about functional stuff?"
timestamp: Date.now() - 49999
,
id: "m_7"
threadID: "t_3"
threadName: "Jill and Brian"
authorName: "Brian"
text: "At ForwardJS? Yeah, of course. See you there!"
timestamp: Date.now() - 39999
]
module.exports = utils

+ 8
- 0
app/routes.cjsx View File

@ -0,0 +1,8 @@
Route = Exim.Router.Route
App = require('./components/App')
MessageSection = require('./components/MessageSection')
module.exports =
<Route handler={App} path="/">
<Route name="message" handler={MessageSection} path="threads/:id" />
</Route>

+ 45
- 0
app/stores/messages.coffee View File

@ -0,0 +1,45 @@
utils = require('lib/utils')
actions = require('actions/messages')
ThreadActions = require('actions/threads')
ThreadStore = require('./threads')
module.exports = Exim.createStore
actions: actions,
getInitial: ->
messages: []
recieveMessages: ->
threadID = ThreadStore.get('currentID')
messages = utils.getAndParse('messages')
filtered = messages
.filter (message) ->
message.threadID == threadID
.map(utils.dateSetter)
.sort(utils.dateComparator)
@update({messages: filtered})
createMessage:
on: (text) ->
timestamp = Date.now()
message =
id: 'm_' + timestamp,
threadID: ThreadStore.get('currentID'),
text: text,
isRead: true,
authorName: 'Bill',
date: new Date(timestamp),
timestamp: timestamp
localStorageItems = JSON.parse(localStorage.getItem('messages'))
localStorageItems.push(message)
localStorage.setItem('messages', JSON.stringify(localStorageItems))
storeItems = @get('messages')
storeItems.push(message)
@update('messages', storeItems)
message
did: (message) ->
ThreadActions.updateLast(message)

+ 42
- 0
app/stores/threads.coffee View File

@ -0,0 +1,42 @@
actions = require("actions/threads")
utils = require("lib/utils")
module.exports = Exim.createStore
actions: actions
getInitial: ->
threads: {}
currentID: null
unread: 0
recieveThreads: ->
messages = utils.getAndParse('messages')
.map(utils.dateSetter)
.sort(utils.dateComparator)
threads = utils.getThreads(messages)
@update 'threads', threads
didRecieveThreads: ->
actions.updateUnread()
updateCurrent: (id) ->
@update 'currentID', id
updateLast: (message) ->
threads = @get('threads')
thread = threads[message.threadID]
thread.lastMessage = message
@update 'threads', threads
updateUnread: (threadId, value) ->
lastMessage = undefined
threads = @get('threads')
if threadId
thread = threads[threadId]
thread.lastMessage.isRead = value or true
@update 'threads', threads
unread = 0
for key of threads
lastMessage = threads[key].lastMessage
unread++ if lastMessage and not lastMessage.isRead
@update 'unread', unread

+ 98
- 0
app/styles/app.css View File

@ -0,0 +1,98 @@
/**
* This file is provided by Facebook for testing and evaluation purposes
* only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
.chatapp {
font-family: 'Muli', 'Helvetica Neue', helvetica, arial;
max-width: 760px;
margin: 20px auto;
overflow: hidden;
}
.message-list, .thread-list {
border: 1px solid #ccf;
font-size: 16px;
height: 400px;
margin: 0;
overflow-y: auto;
padding: 0;
}
.message-section {
float: right;
width: 65%;
}
.thread-section {
float: left;
width: 32.5%;
}
.message-thread-heading,
.thread-count {
height: 40px;
margin: 0;
}
.message-list-item, .thread-list-item {
list-style: none;
padding: 12px 14px 14px;
}
.thread-list-item {
border-bottom: 1px solid #ccc;
cursor: pointer;
}
.thread-list:hover .thread-list-item:hover {
background-color: #f8f8ff;
}
.thread-list:hover .thread-list-item {
background-color: #fff;
}
.thread-list-item.active,
.thread-list:hover .thread-list-item.active,
.thread-list:hover .thread-list-item.active:hover {
background-color: #efefff;
cursor: default;
}
.message-author-name,
.thread-name {
color: #66c;
float: left;
font-size: 13px;
margin: 0;
}
.message-time, .thread-time {
color: #aad;
float: right;
font-size: 12px;
}
.message-text, .thread-last-message {
clear: both;
font-size: 14px;
padding-top: 10px;
}
.message-composer {
box-sizing: border-box;
font-family: inherit;
font-size: 14px;
height: 5em;
width: 100%;
margin: 20px 0 0;
padding: 10px;
}

+ 11
- 3
bower.json View File

@ -3,8 +3,16 @@
"version": "0.0.1",
"main": "public/app.js",
"dependencies": {
"exim": "~0.3.0",
"normalize-css": "~3.0.2"
"exim": "~0.4.0",
"bluebird": "~2.3.6",
"react": "~0.12.1",
"react-router": "*"
},
"overrides": {}
"overrides": {
"react-router": {
"dependencies": {
"react": "*"
}
}
}
}

+ 5
- 5
brunch-config.coffee View File

@ -1,11 +1,11 @@
exports.config =
# See http://brunch.io/#documentation for docs.
# @see http://brunch.io/#documentation for docs
files:
javascripts:
joinTo:
'javascripts/app.js': /^app/
'javascripts/vendor.js': /^(?!app)/
'scripts/app.js': /^app/
'scripts/vendor.js': /^(?!app)/
stylesheets:
joinTo: 'stylesheets/app.css'
joinTo: 'styles/app.css'
templates:
joinTo: 'javascripts/app.js'
joinTo: 'scripts/app.js'

+ 2
- 0
package.json View File

@ -15,9 +15,11 @@
"dependencies": {
"auto-reload-brunch": "^1.7.5",
"clean-css-brunch": ">= 1.0 < 1.8",
"coffee-script": "^1.8.0",
"coffee-script-brunch": "^1.8.1",
"css-brunch": ">= 1.0 < 1.8",
"javascript-brunch": ">= 1.0 < 1.8",
"react-coffee-brunch": "^1.7.1",
"uglify-js-brunch": ">= 1.0 < 1.8"
}
}

Loading…
Cancel
Save