vines 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +5 -0
- data/lib/vines/config.rb +1 -1
- data/lib/vines/stream/http.rb +3 -3
- data/lib/vines/version.rb +1 -1
- data/web/chat/coffeescripts/chat.coffee +34 -61
- data/web/chat/javascripts/app.js +1 -1
- data/web/chat/javascripts/chat.js +22 -74
- data/web/chat/stylesheets/chat.css +2 -145
- data/web/lib/coffeescripts/button.coffee +21 -0
- data/web/lib/coffeescripts/filter.coffee +48 -0
- data/web/lib/coffeescripts/session.coffee +63 -48
- data/web/lib/coffeescripts/transfer.coffee +103 -0
- data/web/lib/javascripts/base.js +2 -1
- data/web/lib/javascripts/button.js +31 -0
- data/web/lib/javascripts/filter.js +86 -0
- data/web/lib/javascripts/session.js +43 -77
- data/web/lib/javascripts/transfer.js +134 -0
- data/web/lib/stylesheets/base.css +129 -5
- metadata +8 -2
@@ -0,0 +1,48 @@
|
|
1
|
+
class Filter
|
2
|
+
constructor: (options) ->
|
3
|
+
@list = options.list
|
4
|
+
@icon = options.icon
|
5
|
+
@form = options.form
|
6
|
+
@attrs = options.attrs
|
7
|
+
@open = options.open
|
8
|
+
@close = options.close
|
9
|
+
this.draw()
|
10
|
+
|
11
|
+
draw: ->
|
12
|
+
$(@icon).addClass 'filter-button'
|
13
|
+
form = $('<form class="filter-form" style="display:none;"></form>').appendTo @form
|
14
|
+
text = $('<input class="filter-text" type="search" placeholder="Filter" results="5"/>').appendTo form
|
15
|
+
|
16
|
+
new Button @icon, ICONS.search,
|
17
|
+
scale: 0.5
|
18
|
+
translation: '-8 -8'
|
19
|
+
|
20
|
+
form.submit -> false
|
21
|
+
text.keyup => this.filter(text)
|
22
|
+
text.change => this.filter(text)
|
23
|
+
text.click => this.filter(text)
|
24
|
+
$(@icon).click =>
|
25
|
+
if form.is ':hidden'
|
26
|
+
this.filter(text)
|
27
|
+
form.show()
|
28
|
+
this.open() if this.open
|
29
|
+
else
|
30
|
+
form.hide()
|
31
|
+
form[0].reset()
|
32
|
+
this.filter(text)
|
33
|
+
this.close() if this.close
|
34
|
+
|
35
|
+
filter: (input) ->
|
36
|
+
text = input.val().toLowerCase()
|
37
|
+
if text == ''
|
38
|
+
$('li', @list).show()
|
39
|
+
return
|
40
|
+
|
41
|
+
test = (node, attr) ->
|
42
|
+
val = (node.attr(attr) || '').toLowerCase()
|
43
|
+
val.indexOf(text) != -1
|
44
|
+
|
45
|
+
$('li', @list).each (ix, node) =>
|
46
|
+
node = $ node
|
47
|
+
matches = (true for attr in @attrs when test node, attr)
|
48
|
+
if matches.length > 0 then node.show() else node.hide()
|
@@ -20,7 +20,7 @@ class Session
|
|
20
20
|
callback true
|
21
21
|
this.findRoster =>
|
22
22
|
this.notify('roster')
|
23
|
-
@xmpp.send $
|
23
|
+
@xmpp.send $('<presence/>').get 0
|
24
24
|
this.findCards()
|
25
25
|
|
26
26
|
disconnect: -> @xmpp.disconnect()
|
@@ -44,6 +44,8 @@ class Session
|
|
44
44
|
|
45
45
|
bareJid: -> @xmpp.jid.split('/')[0]
|
46
46
|
|
47
|
+
uniqueId: -> @xmpp.getUniqueId()
|
48
|
+
|
47
49
|
avatar: (jid) ->
|
48
50
|
card = this.loadCard(jid)
|
49
51
|
if card && card.photo
|
@@ -73,8 +75,12 @@ class Session
|
|
73
75
|
|
74
76
|
findCard: (jid, callback) ->
|
75
77
|
return unless jid
|
76
|
-
|
77
|
-
|
78
|
+
node = this.xml """
|
79
|
+
<iq id="#{this.uniqueId()}" to="#{jid}" type="get">
|
80
|
+
<vCard xmlns="vcard-temp"/>
|
81
|
+
</iq>
|
82
|
+
"""
|
83
|
+
this.sendIQ node, (result) ->
|
78
84
|
card = $('vCard', result)
|
79
85
|
photo = $('PHOTO', card)
|
80
86
|
type = $('TYPE', photo).text()
|
@@ -86,74 +92,81 @@ class Session
|
|
86
92
|
vcard = jid: jid, photo: photo, retrieved: new Date()
|
87
93
|
callback if card.size() > 0 then vcard else null
|
88
94
|
|
89
|
-
iq = $iq(type: 'get', to: jid, id: @xmpp.getUniqueId())
|
90
|
-
.c('vCard', xmlns: 'vcard-temp').up()
|
91
|
-
|
92
|
-
@xmpp.sendIQ iq, handler, handler, 5000
|
93
|
-
|
94
95
|
parseRoster: (node) ->
|
95
96
|
$('item', node).map(-> new Contact this ).get()
|
96
97
|
|
97
98
|
findRoster: (callback) ->
|
98
|
-
|
99
|
+
node = $("""
|
100
|
+
<iq id='#{this.uniqueId()}' type="get">
|
101
|
+
<query xmlns="jabber:iq:roster"/>
|
102
|
+
</iq>
|
103
|
+
""")
|
104
|
+
this.sendIQ node.get(0), (result) =>
|
99
105
|
contacts = this.parseRoster(result)
|
100
106
|
@roster[contact.jid] = contact for contact in contacts
|
101
107
|
callback()
|
102
108
|
|
103
|
-
iq = $iq(type: 'get', id: @xmpp.getUniqueId())
|
104
|
-
.c('query', xmlns: 'jabber:iq:roster').up()
|
105
|
-
|
106
|
-
@xmpp.sendIQ iq, handler, handler, 5000
|
107
|
-
|
108
109
|
sendMessage: (jid, message) ->
|
109
|
-
|
110
|
-
|
111
|
-
|
110
|
+
node = this.xml """
|
111
|
+
<message id="#{this.uniqueId()}" to="#{jid}" type="chat">
|
112
|
+
<body></body>
|
113
|
+
</message>
|
114
|
+
"""
|
115
|
+
$('body', node).text message
|
116
|
+
@xmpp.send node
|
112
117
|
|
113
118
|
sendPresence: (away, status) ->
|
114
|
-
|
119
|
+
node = $ '<presence/>'
|
115
120
|
if away
|
116
|
-
|
117
|
-
|
121
|
+
node.append $('<show>xa</show>')
|
122
|
+
node.append $('<status/>').text status if status != 'Away'
|
118
123
|
else
|
119
|
-
|
120
|
-
@xmpp.send
|
124
|
+
node.append $('<status/>').text status if status != 'Available'
|
125
|
+
@xmpp.send node.get 0
|
126
|
+
|
127
|
+
sendIQ: (node, callback) ->
|
128
|
+
@xmpp.sendIQ node, callback, callback, 5000
|
121
129
|
|
122
130
|
updateContact: (contact, add) ->
|
123
|
-
|
124
|
-
.
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
131
|
+
node = $("""
|
132
|
+
<iq id="#{this.uniqueId()}" type="set">
|
133
|
+
<query xmlns="jabber:iq:roster">
|
134
|
+
<item name="" jid="#{contact.jid}"/>
|
135
|
+
</query>
|
136
|
+
</iq>
|
137
|
+
""")
|
138
|
+
$('item', node).attr 'name', contact.name
|
139
|
+
for group in contact.groups
|
140
|
+
$('item', node).append $('<group></group>').text group
|
141
|
+
@xmpp.send node.get 0
|
142
|
+
this.sendSubscribe(contact.jid) if add
|
129
143
|
|
130
144
|
removeContact: (jid) ->
|
131
|
-
|
132
|
-
.
|
133
|
-
|
134
|
-
|
135
|
-
|
145
|
+
node = $("""
|
146
|
+
<iq id="#{this.uniqueId()}" type="set">
|
147
|
+
<query xmlns="jabber:iq:roster">
|
148
|
+
<item jid="#{jid}" subscription="remove"/>
|
149
|
+
</query>
|
150
|
+
</iq>
|
151
|
+
""")
|
152
|
+
@xmpp.send node.get 0
|
136
153
|
|
137
154
|
sendSubscribe: (jid) ->
|
138
|
-
@xmpp.send
|
139
|
-
type: 'subscribe'
|
140
|
-
to: jid
|
141
|
-
id: @xmpp.getUniqueId()
|
142
|
-
).tree()
|
155
|
+
@xmpp.send this.presence jid, 'subscribe'
|
143
156
|
|
144
157
|
sendSubscribed: (jid) ->
|
145
|
-
@xmpp.send
|
146
|
-
type: 'subscribed'
|
147
|
-
to: jid
|
148
|
-
id: @xmpp.getUniqueId()
|
149
|
-
).tree()
|
158
|
+
@xmpp.send this.presence jid, 'subscribed'
|
150
159
|
|
151
160
|
sendUnsubscribed: (jid) ->
|
152
|
-
@xmpp.send
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
161
|
+
@xmpp.send this.presence jid, 'unsubscribed'
|
162
|
+
|
163
|
+
presence: (to, type) ->
|
164
|
+
$("""
|
165
|
+
<presence
|
166
|
+
id="#{this.uniqueId()}"
|
167
|
+
to="#{to}"
|
168
|
+
type="#{type}"/>
|
169
|
+
""").get 0
|
157
170
|
|
158
171
|
handleIq: (node) ->
|
159
172
|
node = $(node)
|
@@ -209,3 +222,5 @@ class Session
|
|
209
222
|
|
210
223
|
notify: (type, obj) ->
|
211
224
|
callback(obj) for callback in (@listeners[type] || [])
|
225
|
+
|
226
|
+
xml: (xml) -> $.parseXML(xml).documentElement
|
@@ -0,0 +1,103 @@
|
|
1
|
+
class Transfer
|
2
|
+
constructor: (options) ->
|
3
|
+
@session = options.session
|
4
|
+
@file = options.file
|
5
|
+
@to = options.to
|
6
|
+
@progress = options.progress
|
7
|
+
@complete = options.complete
|
8
|
+
@chunks = new Chunks(@file)
|
9
|
+
@opened = false
|
10
|
+
@closed = false
|
11
|
+
@sid = @session.uniqueId()
|
12
|
+
@seq = 0
|
13
|
+
@sent = 0
|
14
|
+
|
15
|
+
start: ->
|
16
|
+
node = $("""
|
17
|
+
<iq id="#{@session.uniqueId()}" to="#{@to}" type="set">
|
18
|
+
<si xmlns="http://jabber.org/protocol/si" id="#{@session.uniqueId()}" profile="http://jabber.org/protocol/si/profile/file-transfer">
|
19
|
+
<file xmlns="http://jabber.org/protocol/si/profile/file-transfer" name="" size="#{@file.size}"/>
|
20
|
+
<feature xmlns="http://jabber.org/protocol/feature-neg">
|
21
|
+
<x xmlns="jabber:x:data" type="form">
|
22
|
+
<field var="stream-method" type="list-single">
|
23
|
+
<option><value>http://jabber.org/protocol/ibb</value></option>
|
24
|
+
</field>
|
25
|
+
</x>
|
26
|
+
</feature>
|
27
|
+
</si>
|
28
|
+
</iq>
|
29
|
+
""")
|
30
|
+
$('file', node).attr 'name', @file.name
|
31
|
+
|
32
|
+
callback = (result) =>
|
33
|
+
methods = $('si feature x field[var="stream-method"] value', result)
|
34
|
+
ok = (true for m in methods when $(m).text() == 'http://jabber.org/protocol/ibb').length > 0
|
35
|
+
this.open() if ok
|
36
|
+
|
37
|
+
@session.sendIQ node.get(0), callback
|
38
|
+
|
39
|
+
open: ->
|
40
|
+
node = $("""
|
41
|
+
<iq id="#{@session.uniqueId()}" to="#{@to}" type="set">
|
42
|
+
<open xmlns="http://jabber.org/protocol/ibb" sid="#{@sid}" block-size="4096"/>
|
43
|
+
</iq>
|
44
|
+
""")
|
45
|
+
callback = (result) =>
|
46
|
+
if this.ok result
|
47
|
+
@opened = true
|
48
|
+
@chunks.start => this.sendChunk()
|
49
|
+
@session.sendIQ node.get(0), callback
|
50
|
+
|
51
|
+
sendChunk: ->
|
52
|
+
return if @closed
|
53
|
+
unless chunk = @chunks.chunk()
|
54
|
+
this.close()
|
55
|
+
return
|
56
|
+
|
57
|
+
node = $("""
|
58
|
+
<iq id="#{@session.uniqueId()}" to="#{@to}" type="set">
|
59
|
+
<data xmlns="http://jabber.org/protocol/ibb" sid="#{@sid}" seq="#{@seq++}">#{chunk}</data>
|
60
|
+
</iq>
|
61
|
+
""")
|
62
|
+
@seq = 0 if @seq > 65535
|
63
|
+
callback = (result) =>
|
64
|
+
return unless this.ok result
|
65
|
+
pct = Math.ceil ++@sent / @chunks.total * 100
|
66
|
+
this.progress pct
|
67
|
+
this.sendChunk()
|
68
|
+
@session.sendIQ node.get(0), callback
|
69
|
+
|
70
|
+
close: ->
|
71
|
+
return if @closed
|
72
|
+
@closed = true
|
73
|
+
node = $("""
|
74
|
+
<iq id="#{@session.uniqueId()}" to="#{@to}" type="set">
|
75
|
+
<close xmlns="http://jabber.org/protocol/ibb" sid="#{@sid}"/>
|
76
|
+
</iq>
|
77
|
+
""")
|
78
|
+
@session.sendIQ node.get(0), ->
|
79
|
+
this.complete()
|
80
|
+
|
81
|
+
stop: ->
|
82
|
+
if @opened
|
83
|
+
this.close()
|
84
|
+
else
|
85
|
+
this.complete()
|
86
|
+
|
87
|
+
ok: (result) -> $(result).attr('type') == 'result'
|
88
|
+
|
89
|
+
class Chunks
|
90
|
+
constructor: (@file) ->
|
91
|
+
@chunks = []
|
92
|
+
@total = 0
|
93
|
+
|
94
|
+
chunk: -> @chunks.shift()
|
95
|
+
|
96
|
+
start: (callback) ->
|
97
|
+
reader = new FileReader()
|
98
|
+
reader.onload = (event) =>
|
99
|
+
data = btoa event.target.result
|
100
|
+
@chunks = (chunk for chunk in data.split /(.{1,4096})/ when chunk)
|
101
|
+
@total = @chunks.length
|
102
|
+
callback()
|
103
|
+
reader.readAsBinaryString @file
|