vines-services 0.1.0

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.
Files changed (69) hide show
  1. data/LICENSE +19 -0
  2. data/README +40 -0
  3. data/Rakefile +130 -0
  4. data/bin/vines-services +95 -0
  5. data/conf/config.rb +25 -0
  6. data/lib/vines/services/command/init.rb +209 -0
  7. data/lib/vines/services/command/restart.rb +14 -0
  8. data/lib/vines/services/command/start.rb +30 -0
  9. data/lib/vines/services/command/stop.rb +20 -0
  10. data/lib/vines/services/command/views.rb +26 -0
  11. data/lib/vines/services/component.rb +26 -0
  12. data/lib/vines/services/config.rb +105 -0
  13. data/lib/vines/services/connection.rb +120 -0
  14. data/lib/vines/services/controller/attributes_controller.rb +19 -0
  15. data/lib/vines/services/controller/base_controller.rb +99 -0
  16. data/lib/vines/services/controller/disco_info_controller.rb +61 -0
  17. data/lib/vines/services/controller/labels_controller.rb +17 -0
  18. data/lib/vines/services/controller/members_controller.rb +44 -0
  19. data/lib/vines/services/controller/messages_controller.rb +66 -0
  20. data/lib/vines/services/controller/probes_controller.rb +45 -0
  21. data/lib/vines/services/controller/services_controller.rb +81 -0
  22. data/lib/vines/services/controller/subscriptions_controller.rb +39 -0
  23. data/lib/vines/services/controller/systems_controller.rb +45 -0
  24. data/lib/vines/services/controller/transfers_controller.rb +58 -0
  25. data/lib/vines/services/controller/uploads_controller.rb +62 -0
  26. data/lib/vines/services/controller/users_controller.rb +127 -0
  27. data/lib/vines/services/core_ext/blather.rb +46 -0
  28. data/lib/vines/services/core_ext/couchrest.rb +33 -0
  29. data/lib/vines/services/indexer.rb +195 -0
  30. data/lib/vines/services/priority_queue.rb +94 -0
  31. data/lib/vines/services/roster.rb +70 -0
  32. data/lib/vines/services/storage/couchdb/fragment.rb +23 -0
  33. data/lib/vines/services/storage/couchdb/service.rb +170 -0
  34. data/lib/vines/services/storage/couchdb/system.rb +141 -0
  35. data/lib/vines/services/storage/couchdb/upload.rb +66 -0
  36. data/lib/vines/services/storage/couchdb/user.rb +137 -0
  37. data/lib/vines/services/storage/couchdb/vcard.rb +13 -0
  38. data/lib/vines/services/storage/couchdb.rb +157 -0
  39. data/lib/vines/services/storage.rb +33 -0
  40. data/lib/vines/services/throttle.rb +26 -0
  41. data/lib/vines/services/version.rb +7 -0
  42. data/lib/vines/services/vql/compiler.rb +94 -0
  43. data/lib/vines/services/vql/vql.citrus +115 -0
  44. data/lib/vines/services/vql/vql.rb +186 -0
  45. data/lib/vines/services.rb +71 -0
  46. data/test/config_test.rb +242 -0
  47. data/test/priority_queue_test.rb +23 -0
  48. data/test/storage/couchdb_test.rb +30 -0
  49. data/test/vql/compiler_test.rb +96 -0
  50. data/test/vql/vql_test.rb +233 -0
  51. data/web/coffeescripts/api.coffee +51 -0
  52. data/web/coffeescripts/commands.coffee +18 -0
  53. data/web/coffeescripts/files.coffee +315 -0
  54. data/web/coffeescripts/init.coffee +21 -0
  55. data/web/coffeescripts/services.coffee +356 -0
  56. data/web/coffeescripts/setup.coffee +503 -0
  57. data/web/coffeescripts/systems.coffee +371 -0
  58. data/web/images/default-service.png +0 -0
  59. data/web/images/linux.png +0 -0
  60. data/web/images/mac.png +0 -0
  61. data/web/images/run.png +0 -0
  62. data/web/images/windows.png +0 -0
  63. data/web/index.html +17 -0
  64. data/web/stylesheets/common.css +52 -0
  65. data/web/stylesheets/files.css +218 -0
  66. data/web/stylesheets/services.css +181 -0
  67. data/web/stylesheets/setup.css +117 -0
  68. data/web/stylesheets/systems.css +142 -0
  69. metadata +230 -0
@@ -0,0 +1,371 @@
1
+ class SystemsPage
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
+ @commands = new Commands
8
+ @chats = {}
9
+ @currentContact = 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
+ node.attr 'data-groups', contact.groups || ''
35
+
36
+ for jid, contact of @session.roster
37
+ found = $("#roster li[data-jid='#{jid}']")
38
+ setName(found, contact)
39
+ if found.length == 0
40
+ if contact.groups[0]=="Vines"
41
+ img_src = "/lib/images/default-service.png"
42
+ else
43
+ img_src = "#{@session.avatar jid}"
44
+ node = $("""
45
+ <li data-jid="#{jid}" data-name="" data-group="" class="offline">
46
+ <span class="text"></span>
47
+ <span class="status-msg">Offline</span>
48
+ <span class="unread" style="display:none;"></span>
49
+ <img class="vcard-img" id="#{jid}-avatar" alt="#{jid}" src="#{img_src}"/>
50
+ </li>
51
+ """).appendTo roster
52
+ setName(node, contact)
53
+ node.click (event) => this.selectContact(event)
54
+
55
+ message: (message) ->
56
+ this.queueMessage message
57
+ me = message.from == @session.jid()
58
+ from = message.from.split('/')[0]
59
+ thread = message.thread
60
+
61
+ if me || from || thread == @currentContact
62
+ bottom = this.atBottom()
63
+ this.appendMessage message
64
+ this.scroll() if bottom
65
+ else
66
+ chat = this.chat message.from
67
+ chat.unread++
68
+ this.eachContact from, (node) ->
69
+ $('.unread', node).text(chat.unread).show()
70
+
71
+ eachContact: (jid, callback) ->
72
+ for node in $("#roster li[data-jid='#{jid}']").get()
73
+ callback $(node)
74
+
75
+ appendMessage: (message) ->
76
+ from = message.from.split('/')[0]
77
+ contact = @session.roster[from]
78
+ name = if contact then (contact.name || from) else from
79
+ name = 'Me' if message.from == @session.jid()
80
+ node = $("""
81
+ <li data-jid="#{from}" style="display:none;">
82
+ <p><pre></pre></p>
83
+ <img alt="#{from}" src="#{@session.avatar from}"/>
84
+ <footer>
85
+ <span class="author"></span>
86
+ <span class="time">#{this.datef message.received}</span>
87
+ </footer>
88
+ </li>
89
+ """).appendTo '#messages'
90
+
91
+ $('pre', node).text message.text
92
+ $('.author', node).text name
93
+ node.fadeIn 200
94
+
95
+ queueMessage: (message) ->
96
+ me = message.from == @session.jid()
97
+ full = message[if me then 'to' else 'from']
98
+ chat = this.chat full
99
+ chat.jid = full
100
+ chat.messages.push message
101
+
102
+ chat: (jid) ->
103
+ bare = jid.split('/')[0]
104
+ chat = @chats[bare]
105
+ unless chat
106
+ chat = jid: jid, messages: [], unread: 0
107
+ @chats[bare] = chat
108
+ chat
109
+
110
+ presence: (presence) ->
111
+ from = presence.from.split('/')[0]
112
+ return if from == @session.bareJid()
113
+ if !presence.type || presence.offline
114
+ contact = @session.roster[from]
115
+ this.eachContact from, (node) ->
116
+ $('.status-msg', node).text contact.status()
117
+ if contact.offline()
118
+ node.addClass 'offline'
119
+ else
120
+ node.removeClass 'offline'
121
+
122
+ if presence.offline
123
+ this.chat(from).jid = from
124
+
125
+ if presence.type == 'subscribe'
126
+ node = $("""
127
+ <li data-jid="#{presence.from}" style="display:none;">
128
+ <form class="inset">
129
+ <h2>Buddy Approval</h2>
130
+ <p>#{presence.from} wants to add you as a buddy.</p>
131
+ <fieldset class="buttons">
132
+ <input type="button" value="Decline"/>
133
+ <input type="submit" value="Accept"/>
134
+ </fieldset>
135
+ </form>
136
+ </li>
137
+ """).appendTo '#notifications'
138
+ node.fadeIn 200
139
+ $('form', node).submit => this.acceptContact node, presence.from
140
+ $('input[type="button"]', node).click => this.rejectContact node, presence.from
141
+
142
+ acceptContact: (node, jid) ->
143
+ node.fadeOut 200, -> node.remove()
144
+ @session.sendSubscribed jid
145
+ @session.sendSubscribe jid
146
+ false
147
+
148
+ rejectContact: (node, jid) ->
149
+ node.fadeOut 200, -> node.remove()
150
+ @session.sendUnsubscribed jid
151
+
152
+ selectContact: (event) ->
153
+ jid = $(event.currentTarget).attr 'data-jid'
154
+ contact = @session.roster[jid]
155
+ return if @currentContact == jid
156
+ @currentContact = jid
157
+
158
+ $('#roster li').removeClass 'selected'
159
+ $(event.currentTarget).addClass 'selected'
160
+ $('#chat-title').text('Chat with ' + (contact.name || contact.jid))
161
+ $('#messages').empty()
162
+
163
+ chat = @chats[jid]
164
+ messages = []
165
+ if chat
166
+ messages = chat.messages
167
+ chat.unread = 0
168
+ this.eachContact jid, (node) ->
169
+ $('.unread', node).text('').hide()
170
+
171
+ this.appendMessage msg for msg in messages
172
+ this.scroll()
173
+
174
+ $('#remove-contact-msg').html "Are you sure you want to remove " +
175
+ "<strong>#{@currentContact}</strong> from your buddy list?"
176
+ $('#remove-contact-form .buttons').fadeIn 200
177
+
178
+ $('#edit-contact-jid').text @currentContact
179
+ $('#edit-contact-name').val @session.roster[@currentContact].name
180
+ $('#edit-contact-form input').fadeIn 200
181
+ $('#edit-contact-form .buttons').fadeIn 200
182
+
183
+ scroll: ->
184
+ msgs = $ '#messages'
185
+ msgs.animate(scrollTop: msgs.prop('scrollHeight'), 400)
186
+
187
+ atBottom: ->
188
+ msgs = $('#messages')
189
+ bottom = msgs.prop('scrollHeight') - msgs.height()
190
+ msgs.scrollTop() == bottom
191
+
192
+ send: ->
193
+ return false unless @currentContact
194
+ input = $('#message')
195
+ text = input.val().trim()
196
+ if text
197
+ chat = @chats[@currentContact]
198
+ jid = if chat then chat.jid else @currentContact
199
+ this.message
200
+ from: @session.jid()
201
+ text: text
202
+ to: jid
203
+ received: new Date()
204
+ @session.sendMessage jid, text
205
+ @commands.push text
206
+ input.val ''
207
+ false
208
+
209
+ addContact: ->
210
+ this.toggleForm '#add-contact-form'
211
+ contact =
212
+ jid: $('#add-contact-jid').val()
213
+ name: $('#add-contact-name').val()
214
+ groups: ['Buddies']
215
+ @session.updateContact contact, true if contact.jid
216
+ false
217
+
218
+ removeContact: ->
219
+ this.toggleForm '#remove-contact-form'
220
+ @session.removeContact @currentContact
221
+ @currentContact = null
222
+
223
+ $('#chat-title').text 'Select a buddy to chat'
224
+ $('#messages').empty()
225
+
226
+ $('#remove-contact-msg').html "Select a buddy in the list above to remove."
227
+ $('#remove-contact-form .buttons').hide()
228
+
229
+ $('#edit-contact-jid').text "Select a buddy in the list above to update."
230
+ $('#edit-contact-name').val ''
231
+ $('#edit-contact-form input').hide()
232
+ $('#edit-contact-form .buttons').hide()
233
+ false
234
+
235
+ updateContact: ->
236
+ this.toggleForm '#edit-contact-form'
237
+ contact =
238
+ jid: @currentContact
239
+ name: $('#edit-contact-name').val()
240
+ groups: @session.roster[@currentContact].groups
241
+ @session.updateContact contact
242
+ false
243
+
244
+ toggleForm: (form, fn) ->
245
+ form = $(form)
246
+ $('form.overlay').each ->
247
+ $(this).hide() unless this.id == form.attr 'id'
248
+ if form.is ':hidden'
249
+ fn() if fn
250
+ form.fadeIn 100
251
+ else
252
+ form.fadeOut 100, ->
253
+ form[0].reset()
254
+ fn() if fn
255
+
256
+ draw: ->
257
+ unless @session.connected()
258
+ window.location.hash = ''
259
+ return
260
+
261
+ $('body').attr 'id', 'servers-page'
262
+ $('#container').hide().empty()
263
+ $("""
264
+ <div id="alpha" class="sidebar column y-fill">
265
+ <h2>Buddies <div id="search-roster-icon"></div></h2>
266
+ <div id="search-roster-form"></div>
267
+ <ul id="roster" class="selectable scroll y-fill"></ul>
268
+ <div id="alpha-controls" class="controls">
269
+ <div id="add-contact"></div>
270
+ <div id="remove-contact"></div>
271
+ <div id="edit-contact"></div>
272
+ </div>
273
+ <form id="add-contact-form" class="overlay" style="display:none;">
274
+ <h2>Add Buddy</h2>
275
+ <input id="add-contact-jid" type="email" maxlength="1024" placeholder="Account name"/>
276
+ <input id="add-contact-name" type="text" maxlength="1024" placeholder="Real name"/>
277
+ <fieldset class="buttons">
278
+ <input id="add-contact-cancel" type="button" value="Cancel"/>
279
+ <input id="add-contact-ok" type="submit" value="Add"/>
280
+ </fieldset>
281
+ </form>
282
+ <form id="remove-contact-form" class="overlay" style="display:none;">
283
+ <h2>Remove Buddy</h2>
284
+ <p id="remove-contact-msg">Select a buddy in the list above to remove.</p>
285
+ <fieldset class="buttons" style="display:none;">
286
+ <input id="remove-contact-cancel" type="button" value="Cancel"/>
287
+ <input id="remove-contact-ok" type="submit" value="Remove"/>
288
+ </fieldset>
289
+ </form>
290
+ <form id="edit-contact-form" class="overlay" style="display:none;">
291
+ <h2>Update Profile</h2>
292
+ <p id="edit-contact-jid">Select a buddy in the list above to update.</p>
293
+ <input id="edit-contact-name" type="text" maxlength="1024" placeholder="Real name" style="display:none;"/>
294
+ <fieldset class="buttons" style="display:none;">
295
+ <input id="edit-contact-cancel" type="button" value="Cancel"/>
296
+ <input id="edit-contact-ok" type="submit" value="Save"/>
297
+ </fieldset>
298
+ </form>
299
+ </div>
300
+ <div id="beta" class="primary column x-fill y-fill">
301
+ <h2 id="chat-title">Select a buddy or service to start communicating</h2>
302
+ <ul id="messages" class="scroll y-fill"></ul>
303
+ <form id="message-form">
304
+ <input id="message" name="message" type="text" maxlength="1024" placeholder="Type a message and press enter to send"/>
305
+ </form>
306
+ </div>
307
+ <div id="charlie" class="sidebar column y-fill">
308
+ <h2>Notifications</h2>
309
+ <ul id="notifications" class="scroll y-fill"></ul>
310
+ <div id="charlie-controls" class="controls">
311
+ <div id="clear-notices"></div>
312
+ </div>
313
+ </div>
314
+ """).appendTo '#container'
315
+
316
+ this.roster()
317
+
318
+ new Button '#clear-notices', ICONS.no
319
+ new Button '#add-contact', ICONS.plus
320
+ new Button '#remove-contact', ICONS.minus
321
+ new Button '#edit-contact', ICONS.user
322
+
323
+ $('#message').focus -> $('form.overlay').fadeOut()
324
+ $('#message').keyup (e) =>
325
+ switch e.keyCode # up, down keys trigger history
326
+ when 38 then $('#message').val @commands.prev()
327
+ when 40 then $('#message').val @commands.next()
328
+
329
+ $('#message-form').submit => this.send()
330
+
331
+ $('#clear-notices').click -> $('#notifications li').fadeOut 200
332
+
333
+ $('#add-contact').click => this.toggleForm '#add-contact-form'
334
+ $('#remove-contact').click => this.toggleForm '#remove-contact-form'
335
+ $('#edit-contact').click => this.toggleForm '#edit-contact-form', =>
336
+ if @currentContact
337
+ $('#edit-contact-jid').text @currentContact
338
+ $('#edit-contact-name').val @session.roster[@currentContact].name
339
+
340
+ $('#add-contact-cancel').click => this.toggleForm '#add-contact-form'
341
+ $('#remove-contact-cancel').click => this.toggleForm '#remove-contact-form'
342
+ $('#edit-contact-cancel').click => this.toggleForm '#edit-contact-form'
343
+
344
+ $('#add-contact-form').submit => this.addContact()
345
+ $('#remove-contact-form').submit => this.removeContact()
346
+ $('#edit-contact-form').submit => this.updateContact()
347
+
348
+ $('#container').show()
349
+ layout = this.resize()
350
+
351
+ fn = ->
352
+ layout.resize()
353
+ layout.resize() # not sure why two are needed
354
+
355
+ new Filter
356
+ list: '#roster'
357
+ icon: '#search-roster-icon'
358
+ form: '#search-roster-form'
359
+ attrs: ['data-jid', 'data-name']
360
+ open: fn
361
+ close: fn
362
+
363
+ resize: ->
364
+ a = $ '#alpha'
365
+ b = $ '#beta'
366
+ c = $ '#charlie'
367
+ msg = $ '#message'
368
+ form = $ '#message-form'
369
+ new Layout ->
370
+ c.css 'left', a.width() + b.width()
371
+ msg.width form.width() - 32
Binary file
Binary file
Binary file
Binary file
Binary file
data/web/index.html ADDED
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5
+ <meta charset="utf-8">
6
+ <meta name="apple-mobile-web-app-capable" content="yes">
7
+ <title>Vines</title>
8
+ <link rel="shortcut icon" type="image/png" href="/favicon.png">
9
+ <script type="text/javascript" src="/lib/javascripts/base.js"></script>
10
+ <script type="text/javascript" src="javascripts/app.js"></script>
11
+ <link rel="stylesheet" href="/lib/stylesheets/base.css">
12
+ <link rel="stylesheet" href="/lib/stylesheets/login.css">
13
+ <link rel="stylesheet" href="stylesheets/app.css">
14
+ </head>
15
+ <body>
16
+ </body>
17
+ </html>
@@ -0,0 +1,52 @@
1
+ #editor-buttons {
2
+ background: #f8f8f8;
3
+ border-top: 1px solid #dfdfdf;
4
+ height: 50px;
5
+ position: absolute;
6
+ bottom: 0;
7
+ width: 100%;
8
+ }
9
+ #editor-buttons {
10
+ text-align: right;
11
+ }
12
+ #editor-buttons #save {
13
+ position: relative;
14
+ right: 10px;
15
+ top: 10px;
16
+ }
17
+ form.sections h2 {
18
+ background: none;
19
+ border: none;
20
+ padding-left: 0;
21
+ }
22
+ form.sections label {
23
+ display: block;
24
+ font-weight: bold;
25
+ font-size: 9pt;
26
+ color: rgba(0,0,0,0.8);
27
+ margin: 10px 0 -2px 0;
28
+ }
29
+ form.sections input[type="text"],
30
+ form.sections input[type="email"],
31
+ form.sections input[type="password"],
32
+ form.sections textarea {
33
+ width: 90%;
34
+ }
35
+ form.sections section {
36
+ border-bottom: 1px solid #ddd;
37
+ margin: 0 20px;
38
+ }
39
+ form.sections section fieldset {
40
+ padding-left: 30%;
41
+ margin-top: -33px;
42
+ margin-bottom: 10px;
43
+ }
44
+ form.sections section:last-child {
45
+ border-bottom: none;
46
+ margin-bottom: 20px;
47
+ }
48
+ form.sections .error {
49
+ color: #dd2828;
50
+ font-size: 8pt;
51
+ width: 89%;
52
+ }
@@ -0,0 +1,218 @@
1
+ #files-page #container {
2
+ height: 100%;
3
+ }
4
+ #files-page #files-title {
5
+ background: #f8f8f8;
6
+ }
7
+ #files-page #files {
8
+ background: #fff;
9
+ height: 100%;
10
+ list-style: none;
11
+ width: 100%;
12
+ }
13
+ #files-page #files li {
14
+ border-bottom: 1px solid #f0f0f0;
15
+ padding: 10px;
16
+ position: relative;
17
+ }
18
+ #files-page #files li .file-icon {
19
+ float: left;
20
+ }
21
+ #files-page #files li .file-icon > svg {
22
+ height: 30px;
23
+ width: 30px;
24
+ }
25
+ #files-page #files li .file-icon .size {
26
+ color: rgba(0, 0, 0, 0.7);
27
+ display: block;
28
+ font-size: 9pt;
29
+ line-height: 1;
30
+ }
31
+ #files-page #files li h2 {
32
+ border: none;
33
+ line-height: 1;
34
+ padding: 0 0 0 50px;
35
+ }
36
+ #files-page #files li:hover {
37
+ background: #fdfdfd;
38
+ }
39
+ #files-page #files li .file-form {
40
+ opacity: 0;
41
+ position: absolute;
42
+ right: 10px;
43
+ top: 22px;
44
+ -moz-transition: opacity 0.3s;
45
+ -o-transition: opacity 0.3s;
46
+ -webkit-transition: opacity 0.3s;
47
+ transition: opacity 0.3s;
48
+ }
49
+ #files-page #files li:hover .file-form {
50
+ opacity: 1.0;
51
+ }
52
+ #files-page #files li:hover footer {
53
+ color: rgba(0, 0, 0, 0.7);
54
+ }
55
+ #files-page #files li footer {
56
+ color: rgba(0, 0, 0, 0.3);
57
+ font-size: 9pt;
58
+ padding-left: 50px;
59
+ -moz-transition: color 0.3s;
60
+ -o-transition: color 0.3s;
61
+ -webkit-transition: color 0.3s;
62
+ transition: color 0.3s;
63
+ }
64
+ #files-page #files li .time {
65
+ display: block;
66
+ }
67
+ #files-page #files li .labels {
68
+ line-height: 1;
69
+ min-height: 10px;
70
+ width: 80%;
71
+ }
72
+ #files-page #files li .labels li {
73
+ border: none;
74
+ display: inline-block;
75
+ line-height: 1;
76
+ margin-right: 10px;
77
+ margin-bottom: 7px;
78
+ padding: 0 12px 0 0;
79
+ position: relative;
80
+ text-transform: capitalize;
81
+ }
82
+ #files-page #files li .labels li .remove {
83
+ cursor: pointer;
84
+ position: absolute;
85
+ right: 0;
86
+ top: -2px;
87
+ width: 14px;
88
+ }
89
+ #files-page #files li .labels li .remove svg {
90
+ height: 12px;
91
+ width: 12px;
92
+ }
93
+ #files-page #files .add-label-button {
94
+ cursor: pointer;
95
+ display: inline-block;
96
+ margin-right: 3px;
97
+ }
98
+ #files-page #files .add-label-button svg {
99
+ height: 14px;
100
+ width: 14px;
101
+ }
102
+ #files-page #files form.add-label input[type="text"] {
103
+ display: inline-block;
104
+ font-size: 8pt;
105
+ padding: 2px;
106
+ position: relative;
107
+ top: -4px;
108
+ width: 100px;
109
+ }
110
+ #files-page #file-chooser {
111
+ height: 30px;
112
+ opacity: 0;
113
+ position: absolute;
114
+ top: 10px;
115
+ left: 10px;
116
+ }
117
+ #files-page #open-file-chooser {
118
+ height: 30px;
119
+ margin: 0;
120
+ position: absolute;
121
+ top: 10px;
122
+ left: 10px;
123
+ width: 240px;
124
+ }
125
+ #files-page #upload-dnd {
126
+ color: #ababab;
127
+ font-weight: bold;
128
+ font-size: 14px;
129
+ position: absolute;
130
+ top: 27px;
131
+ text-align: center;
132
+ text-shadow: 0 1px 1px #fff;
133
+ width: 260px;
134
+ }
135
+ #files-page #uploads .progress {
136
+ background: #fff;
137
+ border: 1px solid #448ccd;
138
+ border-top: 1px solid #5da8db;
139
+ border-radius: 3px;
140
+ -webkit-box-shadow: 0 1px 1px #fff, inset 0 1px 1px rgba(255, 255, 255, 0.5);
141
+ box-shadow: 0 1px 1px #fff, inset 0 1px 1px rgba(255, 255, 255, 0.5);
142
+ color: #0d4078;
143
+ font-size: 11px;
144
+ font-weight: bold;
145
+ height: 15px;
146
+ line-height: 15px;
147
+ margin: 0 10px 10px 10px;
148
+ position: relative;
149
+ text-align: center;
150
+ text-shadow: 0 1px 1px #a2d8f8;
151
+ }
152
+ #files-page #uploads .progress .text {
153
+ position: absolute;
154
+ left: 0;
155
+ width: 100%;
156
+ }
157
+ #files-page #uploads .progress .meter {
158
+ background: #8dd2f7;
159
+ background: -moz-linear-gradient(#8dd2f7, #58b8f4);
160
+ background: -o-linear-gradient(#8dd2f7, #58b8f4);
161
+ background: -webkit-gradient(linear, left top, left bottom, from(#8dd2f7), to(#58b8f4));
162
+ height: 15px;
163
+ position: absolute;
164
+ top: 0;
165
+ }
166
+ #files-page #uploads .progress .cancel {
167
+ height: 100%;
168
+ width: 16px;
169
+ position: absolute;
170
+ right: 0;
171
+ }
172
+ #files-page #labels,
173
+ #files-page #uploads {
174
+ height: 100%;
175
+ list-style: none;
176
+ text-shadow: 0 1px 1px #fff;
177
+ width: 260px;
178
+ }
179
+ #files-page #labels li .text {
180
+ text-transform: capitalize;
181
+ }
182
+ #files-page #labels li,
183
+ #files-page #uploads li {
184
+ cursor: pointer;
185
+ border-bottom: 1px solid #ddd;
186
+ font-weight: bold;
187
+ min-height: 42px;
188
+ padding: 0 10px;
189
+ position: relative;
190
+ -moz-transition: background 0.3s;
191
+ -o-transition: background 0.3s;
192
+ -webkit-transition: background 0.3s;
193
+ transition: background 0.3s;
194
+ }
195
+ #files-page #uploads li {
196
+ font-weight: normal;
197
+ padding: 10px 0 0 0;
198
+ background: #f8f8f8;
199
+ }
200
+ #files-page #labels li:hover:not(.selected),
201
+ #files-page #uploads li:hover {
202
+ background: rgba(255, 255, 255, 1.0);
203
+ }
204
+ #files-page #labels li.selected .count {
205
+ color: rgba(255, 255, 255, 0.85);
206
+ }
207
+ #files-page #labels .count {
208
+ display: block;
209
+ font-size: 11px;
210
+ font-weight: normal;
211
+ line-height: 11px;
212
+ }
213
+ #files-page #charlie-controls {
214
+ text-align: right;
215
+ }
216
+ #files-page #search-files-form {
217
+ background: #f8f8f8;
218
+ }