vines 0.4.6 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -10,10 +10,11 @@ custom storage implementation that you provide. LDAP authentication can be used
10
10
  so user names and passwords aren't stored in the chat database. SSL encryption
11
11
  is mandatory on all client and server connections.
12
12
 
13
- The Vines XMPP server includes a web chat client. The web application is available
14
- after starting the chat server at http://localhost:5280/chat/.
13
+ The server includes support for web chat clients, using BOSH (XMPP over HTTP). A
14
+ sample web application is available in the
15
+ [vines-web](https://github.com/negativecode/vines-web) gem.
15
16
 
16
- Additional documentation can be found at www.getvines.org.
17
+ Additional documentation can be found at [getvines.org](http://www.getvines.org/).
17
18
 
18
19
  ## Usage
19
20
 
@@ -21,7 +22,6 @@ Additional documentation can be found at www.getvines.org.
21
22
  $ gem install vines
22
23
  $ vines init wonderland.lit
23
24
  $ cd wonderland.lit && vines start
24
- $ open http://localhost:5280/chat/
25
25
  ```
26
26
 
27
27
  Login with your favorite chat program (iChat, Adium, Pidgin, etc.) to start chatting!
data/Rakefile CHANGED
@@ -4,15 +4,12 @@ require 'rake'
4
4
  require 'rake/clean'
5
5
  require 'rake/testtask'
6
6
 
7
- ignore = File.read('web/lib/javascripts/.gitignore')
8
- .split("\n").map {|f| "web/lib/javascripts/#{f}" }
9
-
10
- CLOBBER.include('pkg', 'web/chat/javascripts', *ignore)
7
+ CLOBBER.include('pkg')
11
8
 
12
9
  directory 'pkg'
13
10
 
14
11
  desc 'Build distributable packages'
15
- task :build => [:assets, :pkg] do
12
+ task :build => [:pkg] do
16
13
  system 'gem build vines.gemspec && mv vines-*.gem pkg/'
17
14
  end
18
15
 
@@ -23,35 +20,4 @@ Rake::TestTask.new(:test) do |test|
23
20
  test.warning = false
24
21
  end
25
22
 
26
- desc 'Compile and minimize web assets'
27
- task :assets do
28
- require 'coffee-script'
29
- require 'uglifier'
30
-
31
- # combine and compile library coffeescripts
32
- File.open('web/lib/javascripts/base.js', 'w') do |basejs|
33
- assets = %w[layout button contact filter session transfer router navbar notification login logout]
34
- coffee = assets.inject('') do |sum, name|
35
- sum + File.read("web/lib/coffeescripts/#{name}.coffee")
36
- end
37
- js = %w[jquery jquery.cookie raphael icons strophe].inject('') do |sum, name|
38
- sum + File.read("web/lib/javascripts/#{name}.js")
39
- end
40
- compiled = js + CoffeeScript.compile(coffee)
41
- compressed = Uglifier.compile(compiled)
42
- basejs.write(compressed)
43
- end
44
-
45
- # combine and compile chat application's coffeescripts
46
- Dir.mkdir('web/chat/javascripts') unless File.exists?('web/chat/javascripts')
47
- File.open('web/chat/javascripts/app.js', 'w') do |appjs|
48
- coffee = %w[chat init].inject('') do |sum, name|
49
- sum + File.read("web/chat/coffeescripts/#{name}.coffee")
50
- end
51
- compiled = CoffeeScript.compile(coffee)
52
- compressed = Uglifier.compile(compiled)
53
- appjs.write(compressed)
54
- end
55
- end
56
-
57
23
  task :default => [:clobber, :test, :build]
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Vines
4
- VERSION = '0.4.6'
4
+ VERSION = '0.4.7'
5
5
  end
@@ -24,9 +24,6 @@ Gem::Specification.new do |s|
24
24
  s.add_dependency 'nokogiri', '~> 1.5.10'
25
25
 
26
26
  s.add_development_dependency 'minitest', '~> 5.0.5'
27
- s.add_development_dependency 'coffee-script', '~> 2.2.0'
28
- s.add_development_dependency 'coffee-script-source', '~> 1.6.2'
29
- s.add_development_dependency 'uglifier', '~> 1.3.0'
30
27
  s.add_development_dependency 'rake', '~> 10.1.0'
31
28
 
32
29
  s.required_ruby_version = '>= 1.9.3'
@@ -5,47 +5,24 @@
5
5
  <meta name="apple-mobile-web-app-capable" content="yes"/>
6
6
  <title>Vines</title>
7
7
  <link rel="shortcut icon" type="image/png" href="/favicon.png"/>
8
- <link rel="stylesheet" href="/lib/stylesheets/base.css"/>
9
8
  <style type="text/css">
10
9
  body {
11
- background: -moz-radial-gradient(rgba(26, 55, 98, 0.8), rgba(12, 26, 45, 0.8)), url(/lib/images/dark-gray.png);
12
- background: -ms-radial-gradient(center, 500px 500px, rgba(26, 55, 98, 0.8), rgba(12, 26, 45, 0.8)), url(/lib/images/dark-gray.png);
13
- background: -o-radial-gradient(rgba(26, 55, 98, 0.8), rgba(12, 26, 45, 0.8)), url(/lib/images/dark-gray.png);
14
- background: -webkit-radial-gradient(center, 500px 500px, rgba(26, 55, 98, 0.8), rgba(12, 26, 45, 0.8)), url(/lib/images/dark-gray.png);
15
- display: table;
16
- text-align: center;
17
- width: 100%;
18
- }
19
- header {
20
- display: table-cell;
21
- vertical-align: middle;
22
- width: 100%;
23
- }
24
- h1 {
25
- background: url(/lib/images/logo-large.png) no-repeat center;
26
- color: transparent;
27
- height: 82px;
28
- text-shadow: none;
29
- width: 100%;
10
+ background: rgb(253, 253, 253);
11
+ font-family: "Helvetica Neue", sans-serif;
12
+ font-weight: 200;
13
+ font-size: 13px;
14
+ text-shadow: 0 1px 1px #fff;
30
15
  }
31
16
  p {
32
- color: rgba(255, 255, 255, 0.8);
33
- font-size: 11pt;
34
- margin: 20px auto;
35
- width: 400px;
36
- }
37
- a {
38
- color: inherit;
17
+ position: absolute;
18
+ top: 50%;
19
+ margin-top: -10px;
20
+ width: 100%;
21
+ text-align: center;
39
22
  }
40
23
  </style>
41
24
  </head>
42
25
  <body>
43
- <header>
44
- <h1>Page not found</h1>
45
- <p>
46
- This is not the page you're looking for. You probably wanted the
47
- <a href="/chat/">chat</a> application.
48
- </p>
49
- </header>
26
+ <p>This is not the page you're looking for.</p>
50
27
  </body>
51
28
  </html>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vines
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.4.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-21 00:00:00.000000000 Z
12
+ date: 2013-06-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bcrypt-ruby
@@ -123,54 +123,6 @@ dependencies:
123
123
  - - ~>
124
124
  - !ruby/object:Gem::Version
125
125
  version: 5.0.5
126
- - !ruby/object:Gem::Dependency
127
- name: coffee-script
128
- requirement: !ruby/object:Gem::Requirement
129
- none: false
130
- requirements:
131
- - - ~>
132
- - !ruby/object:Gem::Version
133
- version: 2.2.0
134
- type: :development
135
- prerelease: false
136
- version_requirements: !ruby/object:Gem::Requirement
137
- none: false
138
- requirements:
139
- - - ~>
140
- - !ruby/object:Gem::Version
141
- version: 2.2.0
142
- - !ruby/object:Gem::Dependency
143
- name: coffee-script-source
144
- requirement: !ruby/object:Gem::Requirement
145
- none: false
146
- requirements:
147
- - - ~>
148
- - !ruby/object:Gem::Version
149
- version: 1.6.2
150
- type: :development
151
- prerelease: false
152
- version_requirements: !ruby/object:Gem::Requirement
153
- none: false
154
- requirements:
155
- - - ~>
156
- - !ruby/object:Gem::Version
157
- version: 1.6.2
158
- - !ruby/object:Gem::Dependency
159
- name: uglifier
160
- requirement: !ruby/object:Gem::Requirement
161
- none: false
162
- requirements:
163
- - - ~>
164
- - !ruby/object:Gem::Version
165
- version: 1.3.0
166
- type: :development
167
- prerelease: false
168
- version_requirements: !ruby/object:Gem::Requirement
169
- none: false
170
- requirements:
171
- - - ~>
172
- - !ruby/object:Gem::Version
173
- version: 1.3.0
174
126
  - !ruby/object:Gem::Dependency
175
127
  name: rake
176
128
  requirement: !ruby/object:Gem::Requirement
@@ -313,37 +265,7 @@ files:
313
265
  - conf/config.rb
314
266
  - web/404.html
315
267
  - web/apple-touch-icon.png
316
- - web/chat/coffeescripts/chat.coffee
317
- - web/chat/coffeescripts/init.coffee
318
- - web/chat/index.html
319
- - web/chat/javascripts/app.js
320
- - web/chat/stylesheets/chat.css
321
268
  - web/favicon.png
322
- - web/lib/coffeescripts/button.coffee
323
- - web/lib/coffeescripts/contact.coffee
324
- - web/lib/coffeescripts/filter.coffee
325
- - web/lib/coffeescripts/layout.coffee
326
- - web/lib/coffeescripts/login.coffee
327
- - web/lib/coffeescripts/logout.coffee
328
- - web/lib/coffeescripts/navbar.coffee
329
- - web/lib/coffeescripts/notification.coffee
330
- - web/lib/coffeescripts/router.coffee
331
- - web/lib/coffeescripts/session.coffee
332
- - web/lib/coffeescripts/transfer.coffee
333
- - web/lib/images/dark-gray.png
334
- - web/lib/images/default-user.png
335
- - web/lib/images/light-gray.png
336
- - web/lib/images/logo-large.png
337
- - web/lib/images/logo-small.png
338
- - web/lib/images/white.png
339
- - web/lib/javascripts/base.js
340
- - web/lib/javascripts/icons.js
341
- - web/lib/javascripts/jquery.cookie.js
342
- - web/lib/javascripts/jquery.js
343
- - web/lib/javascripts/raphael.js
344
- - web/lib/javascripts/strophe.js
345
- - web/lib/stylesheets/base.css
346
- - web/lib/stylesheets/login.css
347
269
  - test/cluster/publisher_test.rb
348
270
  - test/cluster/sessions_test.rb
349
271
  - test/cluster/subscriber_test.rb
@@ -420,7 +342,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
420
342
  version: '0'
421
343
  segments:
422
344
  - 0
423
- hash: 4058394923438184029
345
+ hash: -1025629835892307549
424
346
  requirements: []
425
347
  rubyforge_project:
426
348
  rubygems_version: 1.8.23
@@ -1,362 +0,0 @@
1
- class @ChatPage
2
- constructor: (@session) ->
3
- @session.onRoster ( ) => this.roster()
4
- @session.onCard (c) => this.card(c)
5
- @session.onMessage (m) => this.message(m)
6
- @session.onPresence (p) => this.presence(p)
7
- @chats = {}
8
- @currentContact = null
9
- @layout = null
10
-
11
- datef: (millis) ->
12
- d = new Date(millis)
13
- meridian = if d.getHours() >= 12 then ' pm' else ' am'
14
- hour = if d.getHours() > 12 then d.getHours() - 12 else d.getHours()
15
- hour = 12 if hour == 0
16
- minutes = d.getMinutes() + ''
17
- minutes = '0' + minutes if minutes.length == 1
18
- hour + ':' + minutes + meridian
19
-
20
- card: (card) ->
21
- this.eachContact card.jid, (node) =>
22
- $('.vcard-img', node).attr 'src', @session.avatar card.jid
23
-
24
- roster: ->
25
- roster = $('#roster')
26
-
27
- $('li', roster).each (ix, node) =>
28
- jid = $(node).attr('data-jid')
29
- $(node).remove() unless @session.roster[jid]
30
-
31
- setName = (node, contact) ->
32
- $('.text', node).text contact.name || contact.jid
33
- node.attr 'data-name', contact.name || ''
34
-
35
- for jid, contact of @session.roster
36
- found = $("#roster li[data-jid='#{jid}']")
37
- setName(found, contact)
38
- if found.length == 0
39
- node = $("""
40
- <li data-jid="#{jid}" data-name="" class="offline">
41
- <span class="text"></span>
42
- <span class="status-msg">Offline</span>
43
- <span class="unread" style="display:none;"></span>
44
- <img class="vcard-img" alt="#{jid}" src="#{@session.avatar jid}"/>
45
- </li>
46
- """).appendTo roster
47
- setName(node, contact)
48
- node.click (event) => this.selectContact(event)
49
-
50
- message: (message) ->
51
- return unless message.type == 'chat' && message.text
52
- this.queueMessage message
53
- me = message.from == @session.jid()
54
- from = message.from.split('/')[0]
55
-
56
- if me || from == @currentContact
57
- bottom = this.atBottom()
58
- this.appendMessage message
59
- this.scroll() if bottom
60
- else
61
- chat = this.chat message.from
62
- chat.unread++
63
- this.eachContact from, (node) ->
64
- $('.unread', node).text(chat.unread).show()
65
-
66
- eachContact: (jid, callback) ->
67
- for node in $("#roster li[data-jid='#{jid}']").get()
68
- callback $(node)
69
-
70
- appendMessage: (message) ->
71
- from = message.from.split('/')[0]
72
- contact = @session.roster[from]
73
- name = if contact then (contact.name || from) else from
74
- name = 'Me' if message.from == @session.jid()
75
- node = $("""
76
- <li data-jid="#{from}" style="display:none;">
77
- <p></p>
78
- <img alt="#{from}" src="#{@session.avatar from}"/>
79
- <footer>
80
- <span class="author"></span>
81
- <span class="time">#{this.datef message.received}</span>
82
- </footer>
83
- </li>
84
- """).appendTo '#messages'
85
-
86
- $('p', node).text message.text
87
- $('.author', node).text name
88
- node.fadeIn 200
89
-
90
- queueMessage: (message) ->
91
- me = message.from == @session.jid()
92
- full = message[if me then 'to' else 'from']
93
- chat = this.chat full
94
- chat.jid = full
95
- chat.messages.push message
96
-
97
- chat: (jid) ->
98
- bare = jid.split('/')[0]
99
- chat = @chats[bare]
100
- unless chat
101
- chat = jid: jid, messages: [], unread: 0
102
- @chats[bare] = chat
103
- chat
104
-
105
- presence: (presence) ->
106
- from = presence.from.split('/')[0]
107
- return if from == @session.bareJid()
108
- if !presence.type || presence.offline
109
- contact = @session.roster[from]
110
- this.eachContact from, (node) ->
111
- $('.status-msg', node).text contact.status()
112
- if contact.offline()
113
- node.addClass 'offline'
114
- else
115
- node.removeClass 'offline'
116
-
117
- if presence.offline
118
- this.chat(from).jid = from
119
-
120
- if presence.type == 'subscribe'
121
- node = $("""
122
- <li data-jid="#{presence.from}" style="display:none;">
123
- <form class="inset">
124
- <h2>Buddy Approval</h2>
125
- <p>#{presence.from} wants to add you as a buddy.</p>
126
- <fieldset class="buttons">
127
- <input type="button" value="Decline"/>
128
- <input type="submit" value="Accept"/>
129
- </fieldset>
130
- </form>
131
- </li>
132
- """).appendTo '#notifications'
133
- node.fadeIn 200
134
- $('form', node).submit => this.acceptContact node, presence.from
135
- $('input[type="button"]', node).click => this.rejectContact node, presence.from
136
-
137
- acceptContact: (node, jid) ->
138
- node.fadeOut 200, -> node.remove()
139
- @session.sendSubscribed jid
140
- @session.sendSubscribe jid
141
- false
142
-
143
- rejectContact: (node, jid) ->
144
- node.fadeOut 200, -> node.remove()
145
- @session.sendUnsubscribed jid
146
-
147
- selectContact: (event) ->
148
- jid = $(event.currentTarget).attr 'data-jid'
149
- contact = @session.roster[jid]
150
- return if @currentContact == jid
151
- @currentContact = jid
152
-
153
- $('#roster li').removeClass 'selected'
154
- $(event.currentTarget).addClass 'selected'
155
- $('#chat-title').text('Chat with ' + (contact.name || contact.jid))
156
- $('#messages').empty()
157
-
158
- chat = @chats[jid]
159
- messages = []
160
- if chat
161
- messages = chat.messages
162
- chat.unread = 0
163
- this.eachContact jid, (node) ->
164
- $('.unread', node).text('').hide()
165
-
166
- this.appendMessage msg for msg in messages
167
- this.scroll()
168
-
169
- $('#remove-contact-msg').html "Are you sure you want to remove " +
170
- "<strong>#{@currentContact}</strong> from your buddy list?"
171
- $('#remove-contact-form .buttons').fadeIn 200
172
-
173
- $('#edit-contact-jid').text @currentContact
174
- $('#edit-contact-name').val @session.roster[@currentContact].name
175
- $('#edit-contact-form input').fadeIn 200
176
- $('#edit-contact-form .buttons').fadeIn 200
177
-
178
- scroll: ->
179
- msgs = $ '#messages'
180
- msgs.animate(scrollTop: msgs.prop('scrollHeight'), 400)
181
-
182
- atBottom: ->
183
- msgs = $('#messages')
184
- bottom = msgs.prop('scrollHeight') - msgs.outerHeight()
185
- msgs.scrollTop() >= bottom
186
-
187
- send: ->
188
- return false unless @currentContact
189
- input = $('#message')
190
- text = input.val().trim()
191
- if text
192
- chat = @chats[@currentContact]
193
- jid = if chat then chat.jid else @currentContact
194
- this.message
195
- from: @session.jid()
196
- text: text
197
- to: jid
198
- type: 'chat'
199
- received: new Date()
200
- @session.sendMessage jid, text
201
- input.val ''
202
- false
203
-
204
- addContact: ->
205
- this.toggleForm '#add-contact-form'
206
- contact =
207
- jid: $('#add-contact-jid').val()
208
- name: $('#add-contact-name').val()
209
- groups: ['Buddies']
210
- @session.updateContact contact, true if contact.jid
211
- false
212
-
213
- removeContact: ->
214
- this.toggleForm '#remove-contact-form'
215
- @session.removeContact @currentContact
216
- @currentContact = null
217
-
218
- $('#chat-title').text 'Select a buddy to chat'
219
- $('#messages').empty()
220
-
221
- $('#remove-contact-msg').html "Select a buddy in the list above to remove."
222
- $('#remove-contact-form .buttons').hide()
223
-
224
- $('#edit-contact-jid').text "Select a buddy in the list above to update."
225
- $('#edit-contact-name').val ''
226
- $('#edit-contact-form input').hide()
227
- $('#edit-contact-form .buttons').hide()
228
- false
229
-
230
- updateContact: ->
231
- this.toggleForm '#edit-contact-form'
232
- contact =
233
- jid: @currentContact
234
- name: $('#edit-contact-name').val()
235
- groups: @session.roster[@currentContact].groups
236
- @session.updateContact contact
237
- false
238
-
239
- toggleForm: (form, fn) ->
240
- form = $(form)
241
- $('form.overlay').each ->
242
- $(this).hide() unless this.id == form.attr 'id'
243
- if form.is ':hidden'
244
- fn() if fn
245
- form.fadeIn 100
246
- else
247
- form.fadeOut 100, =>
248
- form[0].reset()
249
- @layout.resize()
250
- fn() if fn
251
-
252
- draw: ->
253
- unless @session.connected()
254
- window.location.hash = ''
255
- return
256
-
257
- $('body').attr 'id', 'chat-page'
258
- $('#container').hide().empty()
259
- $("""
260
- <div id="alpha" class="sidebar column y-fill">
261
- <h2>Buddies <div id="search-roster-icon"></div></h2>
262
- <div id="search-roster-form"></div>
263
- <ul id="roster" class="selectable scroll y-fill"></ul>
264
- <div id="alpha-controls" class="controls">
265
- <div id="add-contact"></div>
266
- <div id="remove-contact"></div>
267
- <div id="edit-contact"></div>
268
- </div>
269
- <form id="add-contact-form" class="overlay" style="display:none;">
270
- <h2>Add Buddy</h2>
271
- <input id="add-contact-jid" type="email" maxlength="1024" placeholder="Account name"/>
272
- <input id="add-contact-name" type="text" maxlength="1024" placeholder="Real name"/>
273
- <fieldset class="buttons">
274
- <input id="add-contact-cancel" type="button" value="Cancel"/>
275
- <input id="add-contact-ok" type="submit" value="Add"/>
276
- </fieldset>
277
- </form>
278
- <form id="remove-contact-form" class="overlay" style="display:none;">
279
- <h2>Remove Buddy</h2>
280
- <p id="remove-contact-msg">Select a buddy in the list above to remove.</p>
281
- <fieldset class="buttons" style="display:none;">
282
- <input id="remove-contact-cancel" type="button" value="Cancel"/>
283
- <input id="remove-contact-ok" type="submit" value="Remove"/>
284
- </fieldset>
285
- </form>
286
- <form id="edit-contact-form" class="overlay" style="display:none;">
287
- <h2>Update Profile</h2>
288
- <p id="edit-contact-jid">Select a buddy in the list above to update.</p>
289
- <input id="edit-contact-name" type="text" maxlength="1024" placeholder="Real name" style="display:none;"/>
290
- <fieldset class="buttons" style="display:none;">
291
- <input id="edit-contact-cancel" type="button" value="Cancel"/>
292
- <input id="edit-contact-ok" type="submit" value="Save"/>
293
- </fieldset>
294
- </form>
295
- </div>
296
- <div id="beta" class="primary column x-fill y-fill">
297
- <h2 id="chat-title">Select a buddy to chat</h2>
298
- <ul id="messages" class="scroll y-fill"></ul>
299
- <form id="message-form">
300
- <input id="message" name="message" type="text" maxlength="1024" placeholder="Type a message and press enter to send"/>
301
- </form>
302
- </div>
303
- <div id="charlie" class="sidebar column y-fill">
304
- <h2>Notifications</h2>
305
- <ul id="notifications" class="scroll y-fill"></ul>
306
- <div id="charlie-controls" class="controls">
307
- <div id="clear-notices"></div>
308
- </div>
309
- </div>
310
- """).appendTo '#container'
311
-
312
- this.roster()
313
-
314
- new Button '#clear-notices', ICONS.no
315
- new Button '#add-contact', ICONS.plus
316
- new Button '#remove-contact', ICONS.minus
317
- new Button '#edit-contact', ICONS.user
318
-
319
- $('#message').focus -> $('form.overlay').fadeOut()
320
- $('#message-form').submit => this.send()
321
-
322
- $('#clear-notices').click -> $('#notifications li').fadeOut 200
323
-
324
- $('#add-contact').click => this.toggleForm '#add-contact-form'
325
- $('#remove-contact').click => this.toggleForm '#remove-contact-form'
326
- $('#edit-contact').click => this.toggleForm '#edit-contact-form', =>
327
- if @currentContact
328
- $('#edit-contact-jid').text @currentContact
329
- $('#edit-contact-name').val @session.roster[@currentContact].name
330
-
331
- $('#add-contact-cancel').click => this.toggleForm '#add-contact-form'
332
- $('#remove-contact-cancel').click => this.toggleForm '#remove-contact-form'
333
- $('#edit-contact-cancel').click => this.toggleForm '#edit-contact-form'
334
-
335
- $('#add-contact-form').submit => this.addContact()
336
- $('#remove-contact-form').submit => this.removeContact()
337
- $('#edit-contact-form').submit => this.updateContact()
338
-
339
- $('#container').fadeIn 200
340
- @layout = this.resize()
341
-
342
- fn = =>
343
- @layout.resize()
344
- @layout.resize() # not sure why two are needed
345
-
346
- new Filter
347
- list: '#roster'
348
- icon: '#search-roster-icon'
349
- form: '#search-roster-form'
350
- attrs: ['data-jid', 'data-name']
351
- open: fn
352
- close: fn
353
-
354
- resize: ->
355
- a = $ '#alpha'
356
- b = $ '#beta'
357
- c = $ '#charlie'
358
- msg = $ '#message'
359
- form = $ '#message-form'
360
- new Layout ->
361
- c.css 'left', a.width() + b.width()
362
- msg.width form.width() - 32